Плагин Android Gradle (AGP) — это официальная система сборки для приложений Android. Он включает поддержку компиляции множества различных типов источников и связывания их вместе в приложение, которое можно запустить на физическом устройстве Android или эмуляторе.
AGP содержит точки расширения для плагинов для управления входными данными сборки и расширения его функциональности с помощью новых шагов, которые могут быть интегрированы со стандартными задачами сборки. Предыдущие версии AGP не имели официальных API, четко отделенных от внутренних реализаций. Начиная с версии 7.0, AGP имеет набор официальных, стабильных API , на которые вы можете положиться.
Жизненный цикл API AGP
AGP следует жизненному циклу функций Gradle для обозначения состояния своих API:
- Внутренний : Не предназначен для публичного использования.
- Инкубация : доступно для публичного использования, но не является финальным вариантом, что означает, что они могут быть несовместимы с финальной версией.
- Публично : доступно для публичного использования и стабильно
- Устаревший : больше не поддерживается и заменен новыми API.
Политика устаревания
AGP развивается с прекращением поддержки старых API и их заменой новыми, стабильными API и новым языком домена (DSL). Эта эволюция охватит несколько выпусков AGP, и вы можете узнать больше о ней на временной шкале миграции AGP API/DSL .
Когда API AGP устаревают, для этой миграции или по другой причине, они будут по-прежнему доступны в текущем основном выпуске, но будут генерировать предупреждения. Устаревшие API будут полностью удалены из AGP в последующем основном выпуске. Например, если API устаревает в AGP 7.0, он будет доступен в этой версии и будет генерировать предупреждения. Этот API больше не будет доступен в AGP 8.0.
Чтобы увидеть примеры новых API, используемых в общих настройках сборки, взгляните на рецепты плагина Android Gradle . Они предоставляют примеры общих настроек сборки. Вы также можете найти более подробную информацию о новых API в нашей справочной документации .
Основы сборки Gradle
Это руководство не охватывает всю систему сборки Gradle. Однако оно охватывает минимально необходимый набор концепций, которые помогут вам интегрироваться с нашими API, и ссылается на основную документацию Gradle для дальнейшего чтения.
Мы предполагаем наличие базовых знаний о том, как работает Gradle, включая настройку проектов, редактирование файлов сборки, применение плагинов и запуск задач. Чтобы узнать об основах Gradle в отношении AGP, мы рекомендуем ознакомиться с разделом Configure your build . Чтобы узнать об общей структуре настройки плагинов Gradle, см. Developing Custom Gradle Plugins .
Словарь ленивых типов Gradle
Gradle предлагает ряд типов, которые ведут себя «лениво» или помогают отложить тяжелые вычисления или создание Task
на более поздние этапы сборки. Эти типы лежат в основе многих API Gradle и AGP. Следующий список включает основные типы Gradle, участвующие в ленивом выполнении, и их ключевые методы.
-
Provider<T>
- Предоставляет значение типа
T
(где "T" означает любой тип), которое может быть прочитано во время фазы выполнения с помощьюget()
или преобразовано в новыйProvider<S>
(где "S" означает какой-либо другой тип) с помощью методовmap()
,flatMap()
иzip()
. Обратите внимание, чтоget()
никогда не следует вызывать во время фазы конфигурации.-
map()
: принимает лямбда-выражение и создаетProvider
типаS
,Provider<S>
. Аргумент лямбда дляmap()
принимает значениеT
и создает значениеS
. Лямбда-выражение не выполняется немедленно; вместо этого его выполнение откладывается до момента вызоваget()
для результирующегоProvider<S>
, что делает всю цепочку ленивой. -
flatMap()
: также принимает лямбда и производитProvider<S>
, но лямбда принимает значениеT
и производитProvider<S>
(вместо того, чтобы производить значениеS
напрямую). Используйте flatMap(), когда S не может быть определено во время конфигурации, и вы можете получить толькоProvider<S>
. Практически говоря, если вы использовалиmap()
и в итоге получили тип результатаProvider<Provider<S>>
, это, вероятно, означает, что вам следовало использоватьflatMap()
вместо этого. -
zip()
: позволяет объединить два экземпляраProvider
для создания новогоProvider
со значением, вычисленным с помощью функции, которая объединяет значения из двух входных экземпляровProviders
.
-
-
Property<T>
- Реализует
Provider<T>
, поэтому он также предоставляет значение типаT
. В отличие отProvider<T>
, который доступен только для чтения, вы также можете задать значение дляProperty<T>
. Есть два способа сделать это:- Устанавливайте значение типа
T
напрямую, когда оно доступно, без необходимости отложенных вычислений. - Установите другой
Provider<T>
в качестве источника значенияProperty<T>
. В этом случае значениеT
материализуется только при вызовеProperty.get()
.
- Устанавливайте значение типа
-
TaskProvider
- Реализует
Provider<Task>
. Для генерацииTaskProvider
используйтеtasks.register()
, а неtasks.create()
, чтобы гарантировать, что задачи создаются лениво только тогда, когда они нужны. Вы можете использоватьflatMap()
для доступа к выходным даннымTask
до того, какTask
будет создан, что может быть полезно, если вы хотите использовать выходные данные в качестве входных данных для других экземпляровTask
.
Поставщики и их методы преобразования необходимы для настройки входов и выходов задач ленивым способом, то есть без необходимости заранее создавать все задачи и разрешать значения.
Поставщики также несут информацию о зависимости задач. Когда вы создаете Provider
путем преобразования вывода Task
, эта Task
становится неявной зависимостью Provider
и будет создана и запущена всякий раз, когда разрешается значение Provider
, например, когда это требуется другой Task
.
Вот пример регистрации двух задач, GitVersionTask
и ManifestProducerTask
, с отсрочкой создания экземпляров Task
до тех пор, пока они действительно не потребуются. Входное значение ManifestProducerTask
устанавливается на Provider
полученный из выходных данных GitVersionTask
, поэтому ManifestProducerTask
неявно зависит от GitVersionTask
.
// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
it.gitVersionOutputFile.set(
File(project.buildDir, "intermediates/gitVersionProvider/output")
)
}
...
/**
* Register another task in the configuration block (also executed lazily,
* only if the task is required).
*/
val manifestProducer =
project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
/**
* Connect this task's input (gitInfoFile) to the output of
* gitVersionProvider.
*/
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
Эти две задачи будут выполняться только в том случае, если они явно запрошены. Это может произойти как часть вызова Gradle, например, если вы запускаете ./gradlew debugManifestProducer
или если вывод ManifestProducerTask
связан с какой-то другой задачей и его значение становится обязательным.
Хотя вы будете писать пользовательские задачи, которые потребляют входные данные и/или производят выходные данные, AGP не предлагает публичный доступ к своим собственным задачам напрямую. Они являются деталями реализации, которые могут меняться от версии к версии. Вместо этого AGP предлагает API Variant и доступ к выходным данным своих задач или артефактам сборки , которые вы можете читать и преобразовывать. Для получения дополнительной информации см. API Variant, Артефакты и Задачи в этом документе.
Фазы сборки Gradle
Создание проекта по своей сути является сложным и ресурсоемким процессом, и существуют различные функции, такие как избежание конфигурации задач, проверки актуальности и функция кэширования конфигурации, которые помогают минимизировать время, затрачиваемое на воспроизводимые или ненужные вычисления.
Чтобы применить некоторые из этих оптимизаций, скрипты и плагины Gradle должны подчиняться строгим правилам во время каждой из отдельных фаз сборки Gradle: инициализации, настройки и выполнения. В этом руководстве мы сосредоточимся на фазах настройки и выполнения. Вы можете найти больше информации обо всех фазах в руководстве по жизненному циклу сборки Gradle .
Фаза конфигурации
На этапе конфигурации оцениваются скрипты сборки для всех проектов, которые являются частью сборки, применяются плагины и разрешаются зависимости сборки. Этот этап следует использовать для настройки сборки с использованием объектов DSL и для ленивой регистрации задач и их входов.
Поскольку фаза конфигурации выполняется всегда, независимо от того, какая задача запрашивается для выполнения, особенно важно поддерживать ее компактной и ограничивать любые вычисления, зависящие от входных данных, отличных от самих скриптов сборки. То есть, вы не должны выполнять внешние программы или читать из сети, или выполнять длительные вычисления, которые могут быть отложены до фазы выполнения как надлежащие экземпляры Task
.
Фаза исполнения
На этапе выполнения запрошенные задачи и их зависимые задачи выполняются. В частности, выполняются методы класса Task
, отмеченные @TaskAction
. Во время выполнения задачи вам разрешено читать из входных данных (например, файлов) и разрешать ленивых поставщиков, вызывая Provider<T>.get()
. Разрешение ленивых поставщиков таким образом запускает последовательность вызовов map()
или flatMap()
, которые следуют за информацией о зависимости задачи, содержащейся в поставщике. Задачи выполняются лениво для материализации требуемых значений.
API вариантов, артефакты и задачи
Variant API — это механизм расширения в плагине Android Gradle, который позволяет вам манипулировать различными параметрами, обычно устанавливаемыми с помощью DSL в файлах конфигурации сборки, которые влияют на сборку Android. Variant API также дает вам доступ к промежуточным и конечным артефактам, которые создаются сборкой, таким как файлы классов, объединенный манифест или файлы APK/AAB.
Процесс сборки Android и точки расширения
При взаимодействии с AGP используйте специально созданные точки расширения вместо регистрации типичных обратных вызовов жизненного цикла Gradle (таких как afterEvaluate()
) или настройки явных зависимостей Task
. Задачи, созданные AGP, считаются деталями реализации и не выставляются как публичный API. Вам следует избегать попыток получить экземпляры объектов Task
или угадывать имена Task
и добавлять обратные вызовы или зависимости к этим объектам Task
напрямую.
AGP выполняет следующие шаги для создания и выполнения своих экземпляров Task
, которые в свою очередь производят артефакты сборки. За основными шагами, участвующими в создании объекта Variant
, следуют обратные вызовы, которые позволяют вносить изменения в определенные объекты, созданные как часть сборки. Важно отметить, что все обратные вызовы происходят на этапе конфигурации (описанном на этой странице) и должны выполняться быстро, откладывая любую сложную работу на соответствующие экземпляры Task
на этапе выполнения.
- Разбор DSL : это когда оцениваются скрипты сборки и когда создаются и устанавливаются различные свойства объектов Android DSL из блока
android
. Обратные вызовы API Variant, описанные в следующих разделах, также регистрируются на этом этапе. finalizeDsl()
: Обратный вызов, который позволяет изменять объекты DSL до того, как они будут заблокированы для создания компонента (варианта). ОбъектыVariantBuilder
создаются на основе данных, содержащихся в объектах DSL.Блокировка DSL : DSL теперь заблокирован, и изменения больше невозможны.
beforeVariants()
: Этот обратный вызов может влиять на то, какие компоненты создаются, и на некоторые их свойства черезVariantBuilder
. Он по-прежнему позволяет вносить изменения в поток сборки и создаваемые артефакты.Создание варианта : список компонентов и артефактов, которые будут созданы, теперь окончательный и не может быть изменен.
onVariants()
: в этом обратном вызове вы получаете доступ к созданным объектамVariant
и можете задать значения или поставщиков для содержащихся в них значенийProperty
, которые будут вычисляться лениво.Блокировка вариантов : объекты вариантов теперь заблокированы, и внесение изменений больше невозможно.
Созданные задачи : объекты
Variant
и значения ихProperty
используются для создания экземпляровTask
, необходимых для выполнения сборки.
AGP представляет AndroidComponentsExtension
, который позволяет регистрировать обратные вызовы для finalizeDsl()
, beforeVariants()
и onVariants()
. Расширение доступно в скриптах сборки через блок androidComponents
:
// This is used only for configuring the Android build through DSL.
android { ... }
// The androidComponents block is separate from the DSL.
androidComponents {
finalizeDsl { extension ->
...
}
}
Однако мы рекомендуем сохранять скрипты сборки только для декларативной конфигурации с использованием DSL блока android и перемещать любую пользовательскую императивную логику в buildSrc
или внешние плагины. Вы также можете взглянуть на примеры buildSrc
в нашем репозитории Gradle recipes GitHub, чтобы узнать, как создать плагин в вашем проекте. Вот пример регистрации обратных вызовов из кода плагина:
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
...
}
}
}
Давайте подробнее рассмотрим доступные обратные вызовы и типы вариантов использования, которые ваш плагин может поддерживать в каждом из них:
finalizeDsl(callback: (DslExtensionT) -> Unit)
В этом обратном вызове вы можете получить доступ и изменить объекты DSL, которые были созданы путем анализа информации из блока android
в файлах сборки. Эти объекты DSL будут использоваться для инициализации и настройки вариантов на более поздних этапах сборки. Например, вы можете программно создавать новые конфигурации или переопределять свойства, но помните, что все значения должны быть разрешены во время конфигурации, поэтому они не должны полагаться на какие-либо внешние входные данные. После завершения выполнения этого обратного вызова объекты DSL больше не будут полезны, и вам больше не следует хранить ссылки на них или изменять их значения.
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
extension.buildTypes.create("extra").let {
it.isJniDebuggable = true
}
}
}
}
beforeVariants()
На этом этапе сборки вы получаете доступ к объектам VariantBuilder
, которые определяют варианты, которые будут созданы, и их свойства. Например, вы можете программно отключить определенные варианты, их тесты или изменить значение свойства (например, minSdk
) только для выбранного варианта. Подобно finalizeDsl()
, все предоставленные вами значения должны быть разрешены во время конфигурации и не зависеть от внешних входных данных. Объекты VariantBuilder
не должны изменяться после завершения выполнения обратного вызова beforeVariants()
.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
Обратный вызов beforeVariants()
опционально принимает VariantSelector
, который можно получить с помощью метода selector()
в androidComponentsExtension
. Вы можете использовать его для фильтрации компонентов, участвующих в вызове обратного вызова, на основе их имени, типа сборки или разновидности продукта.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
К моменту вызова onVariants()
все артефакты, которые будут созданы AGP, уже определены, поэтому вы больше не можете их отключить. Однако вы можете изменить некоторые значения, используемые для задач, установив их для атрибутов Property
в объектах Variant
. Поскольку значения Property
будут разрешены только при выполнении задач AGP, вы можете безопасно подключить их к поставщикам из ваших собственных пользовательских задач, которые будут выполнять любые требуемые вычисления, включая чтение из внешних входов, таких как файлы или сеть.
// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
// Gather the output when we are in single mode (no multi-apk).
val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }
// Create version code generating task
val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
}
/**
* Wire version code from the task output.
* map() will create a lazy provider that:
* 1. Runs just before the consumer(s), ensuring that the producer
* (VersionCodeTask) has run and therefore the file is created.
* 2. Contains task dependency information so that the consumer(s) run after
* the producer.
*/
mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}
Внесите сгенерированные исходники в сборку
Ваш плагин может предоставлять несколько типов сгенерированных источников, таких как:
- Код приложения в каталоге
java
- Ресурсы Android в каталоге
res
- Ресурсы Java в каталоге
resources
- Ресурсы Android в каталоге
assets
Полный список источников, которые вы можете добавить, см. в API источников .
Этот фрагмент кода показывает, как добавить пользовательскую исходную папку с именем ${variant.name}
в исходный набор Java с помощью функции addStaticSourceDirectory()
. Затем цепочка инструментов Android обрабатывает эту папку.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
Более подробную информацию смотрите в рецепте addJavaSource .
Этот фрагмент кода показывает, как добавить каталог с ресурсами Android, сгенерированными из пользовательской задачи, в набор источников res
. Процесс аналогичен для других типов источников.
onVariants(selector().withBuildType("release")) { variant ->
// Step 1. Register the task.
val resCreationTask =
project.tasks.register<ResCreatorTask>("create${variant.name}Res")
// Step 2. Register the task output to the variant-generated source directory.
variant.sources.res?.addGeneratedSourceDirectory(
resCreationTask,
ResCreatorTask::outputDirectory)
}
...
// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
@get:OutputFiles
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun taskAction() {
// Step 4. Generate your resources.
...
}
}
Более подробную информацию смотрите в рецепте addCustomAsset .
Доступ к артефактам и их изменение
Помимо возможности изменять простые свойства объектов Variant
, AGP также содержит механизм расширения, который позволяет читать или преобразовывать промежуточные и окончательные артефакты, созданные во время сборки. Например, вы можете прочитать окончательное, объединенное содержимое файла AndroidManifest.xml
в пользовательской Task
, чтобы проанализировать его, или вы можете полностью заменить его содержимое на содержимое файла манифеста, сгенерированного вашей пользовательской Task
.
Список артефактов, поддерживаемых в настоящее время, можно найти в справочной документации по классу Artifact
. Каждый тип артефакта имеет определенные свойства, которые полезно знать:
Мощность
Кардинальность Artifact
представляет собой количество его экземпляров FileSystemLocation
или количество файлов или каталогов типа артефакта. Вы можете получить информацию о кардинальности артефакта, проверив его родительский класс: Артефакты с одним FileSystemLocation
будут подклассом Artifact.Single
; артефакты с несколькими экземплярами FileSystemLocation
будут подклассом Artifact.Multiple
.
Тип FileSystemLocation
Проверить, представляет ли Artifact
файлы или каталоги, можно, посмотрев на его параметризованный тип FileSystemLocation
, который может быть либо RegularFile
, либо Directory
.
Поддерживаемые операции
Каждый класс Artifact
может реализовать любой из следующих интерфейсов для указания поддерживаемых им операций:
-
Transformable
: позволяет использоватьArtifact
в качестве входных данных дляTask
, которая выполняет над ним произвольные преобразования и выводит новую версиюArtifact
. -
Appendable
: Применяется только к артефактам, которые являются подклассамиArtifact.Multiple
. Это означает, что кArtifact
можно добавлять, то есть пользовательскаяTask
может создавать новые экземпляры этого типаArtifact
, которые будут добавлены в существующий список. -
Replaceable
: Применяется только к артефактам, которые являются подклассамиArtifact.Single
. ЗаменяемыйArtifact
может быть заменен совершенно новым экземпляром, созданным как выходTask
.
Помимо трех операций по изменению артефакта, каждый артефакт поддерживает операцию get()
(или getAll()
), которая возвращает Provider
с окончательной версией артефакта (после завершения всех операций над ним).
Несколько плагинов могут добавлять любое количество операций над артефактами в конвейер из обратного вызова onVariants()
, и AGP обеспечит их правильную цепочку, чтобы все задачи выполнялись в нужное время, а артефакты правильно создавались и обновлялись. Это означает, что когда операция изменяет какие-либо выходы путем их добавления, замены или преобразования, следующая операция увидит обновленную версию этих артефактов в качестве входов и т. д.
Точкой входа в операции регистрации является класс Artifacts
. Следующий фрагмент кода показывает, как можно получить доступ к экземпляру Artifacts
из свойства объекта Variant
в обратном вызове onVariants()
.
Затем вы можете передать свой пользовательский TaskProvider
, чтобы получить объект TaskBasedOperation
(1), и использовать его для соединения его входов и выходов с помощью одного из методов wiredWith*
(2).
Точный метод, который вам нужно выбрать, зависит от кардинальности и типа FileSystemLocation
, реализованного Artifact
, который вы хотите преобразовать.
И наконец, вы передаете тип Artifact
методу, представляющему выбранную операцию в объекте *OperationRequest
, который вы получаете в ответ, например, toAppendTo()
, toTransform()
или toCreate()
(3).
androidComponents.onVariants { variant ->
val manifestUpdater = // Custom task that will be used for the transform.
project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
// (1) Register the TaskProvider w.
val variant.artifacts.use(manifestUpdater)
// (2) Connect the input and output files.
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest)
// (3) Indicate the artifact and operation type.
.toTransform(SingleArtifact.MERGED_MANIFEST)
}
В этом примере MERGED_MANIFEST
— это SingleArtifact
, и это RegularFile
. Из-за этого нам нужно использовать метод wiredWithFiles
, который принимает одну ссылку RegularFileProperty
для ввода и одну RegularFileProperty
для вывода. Существуют другие методы wiredWith*
в классе TaskBasedOperation
, которые будут работать для других комбинаций мощности Artifact
и типов FileSystemLocation
.
Чтобы узнать больше о расширении AGP, рекомендуем прочитать следующие разделы руководства по системе сборки Gradle:
- Разработка пользовательских плагинов Gradle
- Реализация плагинов Gradle
- Разработка пользовательских типов задач Gradle
- Ленивая конфигурация
- Избегание конфигурации задачи