Gradle для Android, AAR и условных зависимостей

Краткая форма. Каковы некоторые способы организации кода /POM для AAR, так что приложения, использующие этот AAR, имеют только зависимости, которые им действительно нужны?

Длинная форма:

Предположим, у нас есть приложение, которое зависит от проекта библиотеки Android, упакованного как AAR (L).

L содержит сочетание классов, и любое данное приложение (например, A) будет использовать только подмножество этих классов. Например:

  • L может содержать реализации Fragment для встроенных фрагментов API уровня 11, фрагментов с обратным доступом и фрагментов, обработанных ActionBarSherlock

  • L может содержать реализации Activity для регулярных действий, FragmentActivity, ActionBarActivity и ActionBarSherlock-flavored

  • L может вызывать события через LocalBroadcastManager, Square Otto и greenrobot EventBus

  • И так далее

Эти случаи имеют две основные общности, как я вижу:

  • Приложения обычно будут заботиться только о некоторых классах. Например, приложение, использующее Otto, не заботится о коде, который ссылается на greenrobot EventBus, или на приложение, использующее ActionBarActivity, не будет заботиться о ActionBarSherlock.

  • Если AAR является артефактом в репо, приложениям не понадобятся все возможные зависимости вверх, которые необходимы для построения AAR. Например, для приложения, использующего собственные фрагменты API уровня 11, не потребуется support-v4 или actionbarsherlock, хотя сам AAR им нужен для создания AAR

Если бы мы отправились с JAR вместо AAR и управления зависимостями дампа, это довольно просто. Создание JAR будет иметь зависимости времени компиляции (например, support-v4). Однако приложения, использующие этот JAR, могут пропустить эти зависимости, и пока эти приложения не используют классы, которые действительно нуждаются в этих зависимостях, жизнь хороша.

Однако мне трудно понять, как выполнить то же самое с артефактами AAR и Maven, указанными в файле build.gradle. Если L имеет блок dependencies, ссылающийся на зависимости вверх, приложения будут, в свою очередь, загружать эти зависимости транзитивно, когда приложения зависят от L.

Одно из решений, над которым я уверен, будет работать, это разделить L на несколько библиотек. Например, используя сценарий фрагмента, мы могли бы:

  • L1, который содержит реализацию для родной версии API уровня 11, плюс любой общий код, необходимый для других сценариев. Эта библиотека не будет иметь зависимостей по потоку.

  • L2, который содержит реализацию, использующую backport фрагментов поддержки Android Support. L2 будет иметь зависимости от L1 и от support-v4.

  • L3, который содержит реализацию, в которой используются фрагменты с ароматом Шерлока. L3 будет иметь зависимости от L1 и actionbarsherlock.

Затем приложение будет выбирать, следует ли зависеть от L1, L2 или L3, и поэтому будет получать только необходимые зависимости вверх (если они есть).

Мой вопрос: это лучшее решение? Или есть что-то еще в мире Gradle для Android, AAR и артефактов в стиле Maven, которые позволят приложениям зависеть от одного L? Я обеспокоен возможными комбинаторными взрывами библиотек для обработки разнообразного сочетания зависимостей вверх. Я также обеспокоен приложениями oddball, которые действительно нуждаются в нескольких реализациях, и можем ли мы надежно определять зависимости для них (например, приложение в зависимости от L1 и L2, поскольку это то, что автор этого приложения считает нужным для приложения).

Я знаю, что есть способы для приложения для block исключать зависимости (см. ответ Джозефа Эрла для синтаксиса), поэтому приложение может зависеть от L, но затем блокировать actionbarsherlock upstream зависимость, если она не нужна. Хотя это может сработать, в тех случаях, когда я являюсь автором L, я предпочитаю подход L1/L2/L3, поскольку это кажется более чистым.

Любые другие предложения?

Ответ 1

Я не знаю никакой функции управления зависимостями, которая помогла бы с этим.

Наличие отдельных библиотек со многими зависимостями и возможность удаления нежелательных зависимостей могут работать, но есть некоторые проблемы, которые придут с этим:

  • Вы должны полагаться на пользователей L, чтобы удалить правильные зависимости.
  • У вас будут классы в библиотеке, которые нелегко будет пропустить с помощью Proguard. Proguard не удалит что-либо, что расширяет Fragment/Activity/etc, и L должен предоставлять файлы правил proguard, чтобы не удалять классы, которые расширяют поддержку Fragment/Activity/etc... Это затруднит удаление нежелательных классов.
  • В некоторых реализациях могут быть дополнительные ресурсы, и сейчас мы не можем вырезать ненужные ресурсы.

Я думаю, что расщепление библиотеки - это правильная вещь, но это будет сложно сделать, пока у нас не будет вкусов в библиотечных проектах. Как только у нас это будет, я думаю, что с ним будет намного легче справиться. У вас не будет базы L1 и L2/L3, расширяющей ее. Вместо этого у вас будет одна библиотека, которая генерирует разные варианты, каждая со своими зависимостями.

С размерами аромата вы можете обрабатывать комбинации libs vs event libs, хотя вы бы окончательно получили некоторые взрывы, если вы добавите больше измерений.

Преимущества многовариантной библиотеки заключаются в том, что гораздо проще обмениваться кодами между подмножеством вариантов, не вводя еще больше суббиблиотек. Вы также можете иметь двунаправленные ссылки между основным кодом и вкусами, которые вы не могли бы использовать с L2/L3 в зависимости от L1.

Что касается поддержки Ant/Eclipse, это определенно будет сложным. Я бы окончательно проигнорировал Ant. Если кто-то заботится только о построении командной строки, они должны уже перейти на Gradle. Что касается Eclipse, то в какой-то момент у нас будет поддержка Gradle, но я понимаю, что вы не можете ждать.

Ответ 2

В вашем проекте приложения можно исключить транзитивные зависимости в Gradle (например, исключить включение ActionBarSherlock, если вы знаете, что он не будет использоваться), например

 dependencies {
   compile('org.hibernate:hibernate:3.1') {
     // Excluding a particular transitive dependency:
     exclude module: 'cglib' //by artifact name
     exclude group: 'org.jmock' //by group
     exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
   }
 }

Вы также можете ProGuardDexGuard) сжать и вырезать неиспользуемый код.

Gradle пока не поддерживает зависимостей маркировки как необязательный, поскольку Maven делает (что может быть использовано в проекте библиотеки).

Ответ 3

Ну, первое, что я хотел бы упомянуть, это конфигурации Gradle, которые на самом деле являются отражением конфигураций Ivy: модуль, который вы развертываете в каком-то репозитории, может иметь разные конфигурации, и каждая конфигурация может иметь свой собственный набор зависимостей. http://www.gradle.org/docs/current/userguide/dependency_management.html#ssub:multi_artifact_dependencies

Тем не менее, Maven не подходит для этого случая, поскольку в мире Maven нет конфигураций. Классификаторы артефактов не позволят вам иметь разные зависимости.

Что мы можем сделать с Maven, это удалить объявления зависимостей времени компиляции из POM или пометить их с помощью области provided. Возможность AFAIK указывать только зависимости времени компиляции будет доступна в будущих версиях плагина Android Gradle. https://code.google.com/p/android/issues/detail?id=58626