Грейдлы и вложенные непереходные зависимости

Вот тестовый проект: нажмите

У меня есть тестовый проект Gradle Android с тремя модулями: app , library_a , library_b . app зависит от library_a , тогда library_a зависит от library_b :

Build.gradle (приложение)

 dependencies { ... compile (project(":library_a")){ transitive = false; } } 

Build.gradle (library_a)

 dependencies { ... compile (project(":library_b")){ transitive = false; } } 

Обратите внимание, что я устанавливаю transitive = false потому что я не хочу, чтобы классы из library_b были доступны из app

Каждый модуль имеет только один класс, код довольно прост:

приложение:

 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { //... ClassA classA = new ClassA(); classA.doSomething(); } } 

library_a:

 public class ClassA { public void doSomething(){ Log.i("Test", "Done A!"); ClassB classB = new ClassB(); classB.doSomething(); } } 

library_b:

 public class ClassB { public void doSomething(){ Log.i("Test", "Done B!"); } } 

Ну, вот проблема: я строю свой проект с градлеем. Apk компилируется успешно, но когда я его запускаю, я получаю NoClassDefFoundError.

 I/Test﹕ Done A! E/AndroidRuntime﹕ FATAL EXCEPTION: main java.lang.NoClassDefFoundError: ru.pvolan.library_b.ClassB at ru.pvolan.somelibrary.ClassA.doSomething(ClassA.java:12) ... 

Если я установил transitive = true в обоих файлах .gradle, он работает нормально, но, как я уже отмечал выше, я не хочу, чтобы зависимость была транзитивной, поскольку я не хочу, чтобы ClassB можно было получить из MainActivity – только ClassA ,

Что я делаю не так?

Проблема в том, что library_b – требуемая зависимость. Вы не можете просто исключить его, так как вам нужно, чтобы он находился в пути к классам во время выполнения. Вы эффективно искажаете свои фактические зависимости, чтобы обеспечить соблюдение кодовой конвенции и, следовательно, потерять какое-либо преимущество в использовании системы управления зависимостями, такой как Gradle. Если вы хотите применить черный список классов или пакетов, я бы предложил использовать инструмент анализа исходного кода, такой как PMD. Вот пример правила для черных списков конкретных классов.

Если по какой-то причине это невозможно, вы можете заставить свой вышеприведенный пример «работать», просто добавив library_b в library_b к среде выполнения app .

 dependencies { runtime project(':library_b') } 

Это проблема, которую Gradle упростил в Gradle v3.4.

Если вы конвертируете библиотеку A в v3.4, есть простое исправление.

Gradle 3.4 изменяет конфигурацию «компиляции» на набор конфигураций «api» и «реализация».

Сначала вы должны обновить gradle до 3.4 и использовать плагин java-library вместо java-плагина.

Вы должны использовать конфигурацию «api» на любой банке, которая явно используется в вызовах метода API (тип возврата, входные параметры и т. Д.).

Для всех других банок, которые вы хотите «скрыть» (например, Library B), вы должны использовать конфигурацию «реализация». Поскольку библиотека B используется только в рамках методов реализации, нет необходимости подвергать ее воздействию любых других банок во время компиляции; Однако он все равно должен быть доступен во время выполнения, поэтому Библиотека A может его использовать.

Чтобы реализовать эту библиотеку, скрипт должен заменить

 apply plugin: 'java' dependencies { ... compile (project(":library_b")){ transitive = false; } } 

с

 apply plugin: 'java-library' dependencies { implementation project(":library_b") } 

Это изменение скажет Gradle включить библиотеку B в зависимость от времени приложения , чтобы приложение не могло скомпилировать ее, но библиотека B все еще будет доступна во время исполнения для библиотеки A для использования. Если по какой-то причине приложение в конечном итоге нуждается в библиотеке B в будущем, оно будет вынуждено явно включить библиотеку B в ее список зависимостей, чтобы гарантировать, что она получит желаемую версию.

Более подробное описание и примеры см. В этом описании из Gradle: https://blog.gradle.org/incremental-compiler-avoidance

Вы используете multidex? Когда у меня была такая проблема, я использовал multidex и называл класс из другого модуля. Я мог бы исправить это, отключив multidex и запустив proguard.

UPD

 android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 ... // Enabling multidex support. multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.0' } 

Подробнее о multi dex https://developer.android.com/tools/building/multidex.html

И про proguard http://developer.android.com/tools/help/proguard.html