Плагин 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 рекомендуем ознакомиться с разделом «Настройка сборки» . Об общей структуре настройки плагинов Gradle см. раздел «Разработка пользовательских плагинов 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()вместо 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 вариантов и доступ к выходным данным своих задач или артефактам сборки , которые вы можете читать и преобразовывать. Дополнительную информацию см. в разделах API вариантов, артефакты и задачи в этом документе.
Этапы сборки Gradle
Создание проекта по своей природе является сложным и ресурсоемким процессом, и существуют различные функции, такие как предотвращение изменения конфигурации задач, проверки актуальности и функция кэширования конфигурации, которые помогают минимизировать время, затрачиваемое на воспроизводимые или ненужные вычисления.
Для применения некоторых из этих оптимизаций скрипты и плагины Gradle должны соблюдать строгие правила на каждом из отдельных этапов сборки Gradle: инициализации, настройки и выполнения. В этом руководстве мы сосредоточимся на этапах настройки и выполнения. Более подробную информацию обо всех этапах вы найдете в руководстве по жизненному циклу сборки Gradle .
Этап конфигурации
На этапе настройки выполняются скрипты сборки для всех проектов, входящих в состав сборки, применяются плагины и разрешаются зависимости сборки. Этот этап следует использовать для настройки сборки с помощью объектов DSL, а также для ленивой регистрации задач и их входных данных.
Поскольку этап конфигурации выполняется всегда, независимо от того, какая задача запрашивается для выполнения, особенно важно сделать его максимально простым и ограничить любые вычисления, не зависящие от входных данных, кроме самих скриптов сборки. То есть, не следует запускать внешние программы, считывать данные из сети или выполнять длительные вычисления, которые можно отложить до этапа выполнения в виде полноценных экземпляров Task .
Фаза выполнения
На этапе выполнения выполняются запрошенные задачи и зависимые от них задачи. В частности, выполняются методы класса Task , помеченные аннотацией @TaskAction . Во время выполнения задач разрешается чтение из входных данных (например, файлов) и разрешение ленивых поставщиков путем вызова Provider<T>.get() . Разрешение ленивых поставщиков таким образом запускает последовательность вызовов map() или flatMap() , которые следуют информации о зависимостях задач, содержащейся в поставщике. Задачи выполняются лениво для материализации необходимых значений.
Варианты API, артефакты и задачи
API вариантов — это механизм расширения в плагине Android Gradle, позволяющий управлять различными параметрами, обычно задаваемыми с помощью DSL в файлах конфигурации сборки, которые влияют на сборку Android. 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, которые они содержат, для ленивого вычисления.Блокировка вариантов : Объекты-варианты теперь заблокированы, и внесение изменений больше невозможно.
Созданные задачи : Для создания экземпляров
Task, необходимых для выполнения сборки, используются объекты-Variantи значения ихProperty.
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 .
Этот фрагмент кода показывает, как добавить в набор источников res каталог с ресурсами Android, сгенерированными пользовательской задачей. Процесс аналогичен для других типов источников.
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 , или количество файлов или каталогов данного типа объектов. Информацию о мощности множества объектов Artifact можно получить, проверив их родительский класс: объекты с одним экземпляром 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 . В классе TaskBasedOperation есть и другие методы wiredWith* , которые будут работать для других комбинаций количества Artifact и типов FileSystemLocation .
Чтобы узнать больше о расширении возможностей AGP, рекомендуем ознакомиться со следующими разделами руководства по системе сборки Gradle:
- Разработка пользовательских плагинов Gradle
- Внедрение плагинов Gradle
- Разработка пользовательских типов задач Gradle
- Ленивая конфигурация
- Избегание настройки задачи