Android Gradle 插件 (AGP) 是 Android 应用程序的官方构建系统。它支持编译多种不同类型的源代码,并将其链接成可在实体 Android 设备或模拟器上运行的应用程序。
AGP 包含插件扩展点,用于控制构建输入并通过可与标准构建任务集成的全新步骤来扩展其功能。之前版本的 AGP 没有与内部实现明确分离的官方 API。从 7.0 版开始,AGP 拥有一套您可以信赖的官方稳定版 API。
AGP API 生命周期
AGP 遵循 Gradle 功能生命周期来指定其 API 的状态。
- Internal:不供公共使用
- Incubating:可供公共使用,但尚未最终确定,这意味着它们在最终版本中可能不向后兼容
- Public:可供公共使用且稳定
- Deprecated:不再支持,已被新 API 取代
废弃政策
AGP 正在不断发展,旧 API 被废弃,取而代之的是新的稳定版 API 和新的领域专用语言 (DSL)。这项演变将涵盖多个 AGP 版本,您可以在 AGP API/DSL 迁移时间表中了解更多信息。
当 AGP API 因本次或其他迁移而废弃时,它们将继续在当前主版本中提供,但会生成警告。废弃的 API 将在后续主版本中从 AGP 中完全移除。例如,如果某个 API 在 AGP 7.0 中废弃,它将在该版本中可用并生成警告。该 API 将不再在 AGP 8.0 中可用。
要查看常用构建自定义设置中新 API 的使用示例,请参阅 Android Gradle 插件秘诀。它们提供了常见构建自定义设置的示例。您还可以在我们的参考文档中找到有关新 API 的更多详细信息。
Gradle 构建基础知识
本指南不涵盖整个 Gradle 构建系统。但是,它确实涵盖了帮助您集成我们 API 所需的最少概念集,并链接到主要的 Gradle 文档以供进一步阅读。
我们假设您对 Gradle 的工作原理有基本了解,包括如何配置项目、编辑构建文件、应用插件和运行任务。要了解与 AGP 相关的 Gradle 基础知识,我们建议您查看配置您的构建。要了解自定义 Gradle 插件的通用框架,请参阅 开发自定义 Gradle 插件。
Gradle 延迟类型词汇表
Gradle 提供了许多“延迟”行为的类型,或者有助于将繁重计算或 Task
的创建推迟到构建的后续阶段。这些类型是许多 Gradle 和 AGP API 的核心。以下列表包含参与延迟执行的主要 Gradle 类型及其关键方法。
Provider<T>
- 提供类型为
T
的值(其中“T”表示任何类型),可在执行阶段使用get()
读取,或使用map()
、flatMap()
和zip()
方法转换为新的Provider<S>
(其中“S”表示其他类型)。请注意,在配置阶段不应调用get()
。map()
:接受一个 lambda 表达式并生成类型为S
的Provider
,即Provider<S>
。map()
的 lambda 参数获取值T
并生成值S
。该 lambda 表达式不会立即执行;相反,它的执行会推迟到对结果Provider<S>
调用get()
的那一刻,从而使整个链条变得延迟。flatMap()
:也接受一个 lambda 表达式并生成Provider<S>
,但该 lambda 获取值T
并生成Provider<S>
(而不是直接生成值S
)。当 S 无法在配置时确定,并且您只能获取Provider<S>
时,请使用 flatMap()。实际上,如果您使用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
实例的输入,这会很有用。
Provider 及其转换方法对于以延迟方式设置任务的输入和输出至关重要,即无需预先创建所有任务并解析值。
Provider 也携带任务依赖项信息。当您通过转换 Task
输出创建 Provider
时,该 Task
会成为 Provider
的隐式依赖项,并且只要 Provider
的值被解析(例如,当另一个 Task
需要它时),该 Task
就会被创建并运行。
以下是注册两个任务 GitVersionTask
和 ManifestProducerTask
的示例,同时将 Task
实例的创建推迟到实际需要它们时。将 ManifestProducerTask
输入值设置为从 GitVersionTask
输出获取的 Provider
,因此 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 提供 Variant API 和对其任务输出(或“构建工件”)的访问,您可以读取和转换这些输出。有关更多信息,请参阅本文档中的Variant API、工件和任务。
Gradle 构建阶段
构建项目本质上是一个复杂且资源需求高的过程,并且有各种功能,例如任务配置避免、最新检查和配置缓存功能,有助于最大限度地减少在可重现或不必要的计算上花费的时间。
为了应用其中一些优化,Gradle 脚本和插件必须在每个不同的 Gradle 构建阶段(初始化、配置和执行)中遵守严格的规则。在本指南中,我们将重点介绍配置和执行阶段。您可以在 Gradle 构建生命周期指南中找到有关所有阶段的更多信息。
配置阶段
在配置阶段,会评估构建中所有项目的构建脚本,应用插件,并解析构建依赖项。此阶段应用于使用 DSL 对象配置构建,并延迟注册任务及其输入。
由于无论请求运行哪个任务,配置阶段都会运行,因此保持其精简并限制任何计算不依赖于构建脚本本身以外的输入尤为重要。也就是说,您不应执行外部程序或从网络读取,或执行可能作为适当的 Task
实例推迟到执行阶段的长时间计算。
执行阶段
在执行阶段,会执行请求的任务及其依赖任务。具体来说,会执行带有 @TaskAction
标记的 Task
类方法。在任务执行期间,您可以从输入(例如文件)中读取,并通过调用 Provider<T>.get()
解析延迟的 Provider。以这种方式解析延迟的 Provider 会启动一系列 map()
或 flatMap()
调用,这些调用遵循 Provider 中包含的任务依赖项信息。任务会延迟运行以具体化所需的值。
Variant 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
块中 Android DSL 对象的各种属性。本节中描述的 Variant API 回调也在此阶段注册。 finalizeDsl()
:此回调允许您在 DSL 对象锁定以进行组件(变体)创建之前更改它们。VariantBuilder
对象是根据 DSL 对象中包含的数据创建的。DSL 锁定:DSL 现在已锁定,无法再进行更改。
beforeVariants()
:此回调可以通过VariantBuilder
影响要创建的组件及其某些属性。它仍然允许修改构建流程和生成的工件。变体创建:要创建的组件和工件列表现已最终确定,无法更改。
onVariants()
:在此回调中,您可以访问已创建的Variant
对象,并可以为它们包含的Property
值设置值或 Provider,以进行延迟计算。变体锁定:Variant 对象现在已锁定,无法再进行更改。
任务已创建:
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 ->
...
}
}
但是,我们建议只将构建脚本用于使用 android 块的 DSL 进行声明式配置,并将任何自定义命令式逻辑移动到 buildSrc
或外部插件。您还可以查看我们的 Gradle Recipes GitHub 仓库中的 buildSrc
示例,了解如何在项目中创建插件。以下是注册插件代码回调的示例:
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)
在此回调中,您可以访问和修改通过解析构建文件中 android
块中的信息而创建的 DSL 对象。这些 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
,您可以通过 androidComponentsExtension
上的 selector()
方法获取该选择器。您可以使用它根据组件的名称、构建类型或产品风味来过滤参与回调调用的组件。
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
当 onVariants()
被调用时,AGP 将创建的所有工件都已确定,因此您无法再禁用它们。但是,您可以通过为 Variant
对象中的 Property
属性设置值来修改用于任务的一些值。由于 Property
值仅在 AGP 的任务执行时才会解析,因此您可以安全地将它们连接到您自己的自定义任务中的 Provider,这些任务将执行任何所需的计算,包括从外部输入(例如文件或网络)读取。
// 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
目录中的应用程序代码res
目录中的Android 资源resources
目录中的Java 资源assets
目录中的Android 资源
有关您可以添加的完整源列表,请参阅 Sources API 的参考文档。
此代码段展示了如何使用 addStaticSourceDirectory()
函数将名为 ${variant.name}
的自定义源文件夹添加到 Java 源集中。然后,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 还包含一种扩展机制,可让您读取或转换构建过程中生成的中间和最终工件。例如,您可以在自定义 Task
中读取最终合并的 AndroidManifest.xml
文件内容以对其进行分析,或者您可以用自定义 Task
生成的清单文件内容完全替换它。
您可以在 Artifact
类的参考文档中找到当前支持的工件列表。每个工件类型都有一些有用的属性:
基数
一个 Artifact
的基数表示其 FileSystemLocation
实例的数量,即工件类型的文件或目录数量。您可以通过检查其父类来获取有关工件基数的信息:具有单个 FileSystemLocation
的工件将是 Artifact.Single
的子类;具有多个 FileSystemLocation
实例的工件将是 Artifact.Multiple
的子类。
FileSystemLocation
类型
您可以通过查看其参数化 FileSystemLocation
类型(可以是 RegularFile
或 Directory
)来检查 Artifact
是表示文件还是目录。
支持的操作
每个 Artifact
类都可以实现以下任何接口以指示其支持的操作:
Transformable
:允许将Artifact
用作执行任意转换的Task
的输入,并输出新版本的Artifact
。Appendable
:仅适用于Artifact.Multiple
的子类工件。这意味着Artifact
可以附加,即自定义Task
可以创建此Artifact
类型的新实例,这些实例将添加到现有列表中。Replaceable
:仅适用于Artifact.Single
的子类工件。可替换的Artifact
可以被一个全新的实例替换,该实例作为Task
的输出生成。
除了这三个工件修改操作之外,每个工件都支持 get()
(或 getAll()
)操作,该操作返回一个带有工件最终版本(在所有操作完成后)的 Provider
。
多个插件可以在 onVariants()
回调中将任意数量的工件操作添加到管道中,AGP 将确保它们正确链接,以便所有任务在正确的时间运行,并且工件正确生成和更新。这意味着当操作通过附加、替换或转换来更改任何输出时,下一个操作将把这些工件的更新版本视为输入,依此类推。
注册操作的入口点是 Artifacts
类。以下代码段展示了如何在 onVariants()
回调中从 Variant
对象上的属性访问 Artifacts
实例。
然后,您可以传入您的自定义 TaskProvider
以获取 TaskBasedOperation
对象 (1),并使用它通过其中一个 wiredWith*
方法连接其输入和输出 (2)。
您需要选择的确切方法取决于您要转换的 Artifact
实现的基数和 FileSystemLocation
类型。
最后,您将 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 构建系统手册中的以下部分: