编写 Gradle 插件

Android Gradle 插件 (AGP) 是官方的 Android 应用构建系统。该系统支持编译多种不同类型的源代码,以及将其链接到可在实体 Android 设备或模拟器上运行的应用中。

AGP 包含插件扩展点,用于控制 build 输入并通过可与标准 build 任务集成的新步骤扩展其功能。旧版 AGP 没有与内部实现明确分开的官方 API。从 7.0 版本开始,AGP 将提供一组稳定的官方 API,值得您信赖。

AGP API 生命周期

AGP 会遵照 Gradle 功能生命周期来指定其 API 的状态:

  • 内部:不可公开使用
  • 培育中:可公开使用,但不是最终版本,这意味着它们的最终版本可能无法向后兼容
  • 公开:可公开使用,且很稳定
  • 已废弃:不再受支持,已被新的 API 取代

废弃政策

随着旧版 API 的废弃并代之以稳定的新版 API,并采用新的领域特定语言 (DSL),AGP 不断在发生变化。这种变化将涉及多个 AGP 版本,如需详细了解,请参阅 AGP API/DSL 迁移时间表

因本次迁移或其他原因而废弃 AGP API 后,这些 API 仍可继续在当前的主要版本中使用,但会生成警告。在后续的主要版本中,已废弃的 API 将从 AGP 中完全移除。例如,如果某个 API 在 AGP 7.0 中被废弃,此 API 在该版本中仍然可用,但会生成警告。在 AGP 8.0 中,此 API 将不再可用。

如需查看在常见 build 自定义中使用的新 API 示例,请参阅 Android Gradle 插件诀窍,其中会提供常见 build 自定义示例。您还可以参阅我们的参考文档,详细了解新 API。

Gradle build 基础知识

本指南未涵盖整个 Gradle 构建系统。不过,它涵盖了与我们的 API 集成所需的最基本的概念。该指南还链接到了主 Gradle 文档,以便您进一步了解相关信息。

我们假定您对 Gradle 的工作原理已有基本了解,包括如何配置项目、修改 build 文件、应用插件以及运行任务。如需了解关于 AGP 的 Gradle 基础知识,我们建议您查看配置 build。如需了解用于自定义 Gradle 插件的通用框架,请参阅开发自定义 Gradle 插件

Gradle 延迟类型术语表

Gradle 提供多种类型,它们可表现出“延迟”行为,或者帮助将繁重的计算或 Task 创建推迟到 build 的后续阶段。这些类型是许多 Gradle 和 AGP API 的核心。以下列表包含延迟执行涉及的主要 Gradle 类型及其主要方法。

Provider<T>
提供类型为 T(其中“T”表示任意类型)的值,系统可在执行阶段使用 get() 读取该值,也可使用 map()flatMap()zip() 方法将其转换为新的 Provider<S>(其中“S”表示某些其他类型)。请注意,切勿在配置阶段调用 get()
  • map():接受 lambda 并生成类型为 SProvider,即 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(),以确保任务只在需要时才会延迟实例化。在 Task 创建之前,您可以使用 flatMap() 来访问该 Task 的输出,如果您想将此输出用作其他 Task 实例的输入,这会很实用。

如要以延迟方式设置任务的输入和输出,提供程序及其转换方法至关重要,也就是说,无需预先创建所有任务并解析值。

提供程序还携带有任务依赖项信息。当您通过转换一个 Task 的输出来创建 Provider 时,该 Task 会成为相应 Provider 的隐式依赖项,无论 Provider 的值在何时进行解析(例如当另一个 Task 需要它时),系统都要会创建并运行该 Task。

下面的示例展示了如何注册 GitVersionTaskManifestProducerTask 这两个任务,同时将 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 及对其任务输出的访问权限,或者提供可供您读取和转换的 build 工件。如需了解详情,请参阅本文档的 Variant API、工件和任务部分。

Gradle build 阶段

项目构建本身是一个复杂且耗费资源的过程,并且涉及各种功能,例如任务配置规避、更新检查和配置缓存功能,这些功能有助于最大程度减少花在可重现或非必要计算上的时间。

若要应用其中某些优化功能,Gradle 脚本和插件在以下每个不同的 Gradle build 阶段都必须遵循严格的规则:初始化、配置和执行。在本指南中,我们将重点介绍配置阶段和执行阶段。如需详细了解所有阶段,请参阅 Gradle build 生命周期指南

配置阶段

在配置阶段,系统会评估 build 涉及到的所有项目的 build 脚本、应用插件并解析 build 依赖项。此阶段应该用来使用 DSL 对象配置 build,以及以延迟方式注册任务及其输入。

由于配置阶段总是在运行,因此无论系统请求运行哪个任务,都务必让该阶段保持精简,并对所有计算进行限制,防止其依赖于输入而不是 build 脚本本身。也就是说,您不应执行外部程序或从网络中读取内容,也不应执行可以作为合适的 Task 实例推迟到执行阶段的长时间计算。

执行阶段

在执行阶段,系统会执行请求的任务及其依赖的任务。具体来说,就是会执行标有 @TaskActionTask 类方法。在任务执行阶段,您可以通过调用 Provider<T>.get() 从输入(如文件)中读取内容,也可以解析延迟提供程序。以这种方式解析延迟提供程序时,系统会启动一系列 map()flatMap() 调用,这些调用会遵循提供程序中包含的任务依赖项信息。任务会延迟运行,以具体化所需的值。

Variant API、工件和任务

Variant API 是 Android Gradle 插件中的扩展机制,可让您操纵各种选项,这些选项通常使用可影响 Android build 的 build 配置文件中的 DSL 进行设置。您还可以通过 Variant API 访问 build 期间创建的中间工件和最终工件,例如类文件、合并后的清单或 APK/AAB 文件。

Android build 流程和扩展点

与 AGP 交互时,请使用特制的扩展点,而不要注册典型的 Gradle 生命周期回调(例如 afterEvaluate())或设置明确的 Task 依赖项。系统会将 AGP 创建的任务视为实现细节,不会作为公共 API 公开。您必须避免尝试获取 Task 对象的实例或猜测 Task 名称,还要避免直接向这些 Task 对象添加回调或依赖项。

AGP 需完成以下步骤来创建和执行其 Task 实例,这些实例反过来会再生成 build 工件。在完成创建 Variant 对象所涉及的主要步骤后,接下来就是执行回调,您可以通过回调对在 build 过程中创建的某些对象进行更改。值得注意的是,所有回调都发生在配置阶段(此页面中进行了相关介绍)且必须快速运行,从而将所有复杂工作都推迟到执行阶段合适的 Task 实例。

  1. DSL 解析:发生在系统评估 build 脚本时,以及创建和设置 android 代码块中的 Android DSL 对象的各种属性时。后面几部分中介绍的 Variant API 回调也是在此阶段注册的。
  2. finalizeDsl():您可以通过此回调在 DSL 对象因组件(变体)创建而被锁定之前对其进行更改。VariantBuilder 对象是基于 DSL 对象中包含的数据创建的。

  3. DSL 锁定:DSL 现已被锁定,无法再进行更改。

  4. beforeVariants():此回调可通过 VariantBuilder 影响系统会创建哪些组件以及所创建组件的部分属性。它还支持对 build 流程和生成的工件进行修改。

  5. 变体创建:将要创建的组件和工件列表现已最后确定,无法更改。

  6. onVariants():在此回调中,您可以访问已创建的 Variant 对象,您还可以为它们包含的 Property 值设置值或提供程序,以进行延迟计算。

  7. 变体锁定:变体对象现已被锁定,无法再进行更改。

  8. 任务已创建:使用 Variant 对象及其 Property 值创建执行 build 所必需的 Task 实例。

AGP 引入了 AndroidComponentsExtension,可让您为 finalizeDsl()beforeVariants()onVariants() 注册回调。该扩展通过 androidComponents 代码块在 build 脚本中提供:

// 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 的声明性配置保留 build 脚本,并将所有自定义命令式逻辑迁移至 buildSrc 或外部插件。您也可以看看我们的 Gradle 诀窍 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)

在此回调中,您可以访问和修改通过解析 build 文件中 android 代码块的信息而创建的 DSL 对象。这些 DSL 对象将用来在 build 的后续阶段中初始化和配置变体。例如,您可以程序化地创建新配置或替换属性,但请注意,所有值都必须在配置时进行解析,因此它们不能依赖于任何外部输入。此回调执行完毕后,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()

在 build 过程的这一阶段,您可以访问 VariantBuilder 对象,这些对象决定了将要创建的变体及其属性。例如,您可以程序化地停用某些变体及其测试,或者仅针对所选变体更改某个属性的值(例如 minSdk)。与 finalizeDsl() 类似,您提供的所有值都必须在配置时进行解析,且不得依赖于外部输入。beforeVariants() 回调执行完毕后,您不得修改 VariantBuilder 对象。

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

beforeVariants() 回调会根据需要接受 VariantSelector(您可通过在 androidComponentsExtension 上使用 selector() 方法获得)。您可以使用它根据组件的名称、build 类型或产品变种过滤参与回调调用的组件。

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

在调用 onVariants() 时,AGP 将会创建的所有工件均已确定,因此您无法再停用它们。不过,您可以通过为 Variant 对象中的 Property 属性设置值来修改任务所使用的某些值。由于 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() })
}

向 build 贡献生成的源代码

您的插件可以提供几种类型的生成源代码,例如:

如需查看可添加源代码的完整列表,请参阅 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 还包含扩展机制,可让您读取或转换 build 期间生成的中间工件和最终工件。例如,您可以在自定义 Task 中读取合并后的最终 AndroidManifest.xml 文件内容以对其进行分析,也可以将其内容完全替换为自定义 Task 生成的清单文件的内容。

您可以在 Artifact 类的参考文档中找到目前受支持的工件列表。每种工件类型都有一些值得了解的属性:

基数

一个 Artifact 的基数表示其 FileSystemLocation 实例的数量,或者该工件类型的文件数量或目录数量。如需了解某个工件的基数,请查看其父类:具有单个 FileSystemLocation 的工件是 Artifact.Single 的子类;具有多个 FileSystemLocation 实例的工件是 Artifact.Multiple 的子类。

FileSystemLocation 类型

如需确定一个 Artifact 表示的是文件还是目录,您可以查看其参数化 FileSystemLocation 类型,该类型可能是 RegularFileDirectory

支持的操作

每个 Artifact 类都可以实现以下任一接口,以表明自己支持哪些操作:

  • Transformable:允许将 Artifact 作为输入,供在该接口上执行任意转换并输出新版 ArtifactTask 使用。
  • 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_MANIFESTSingleArtifact,它是一种 RegularFile。因此,我们需要使用 wiredWithFiles 方法,该方法会接受单个 RegularFileProperty 引用作为输入,接受单个 RegularFileProperty 作为输出。TaskBasedOperation 类上还有其他 wiredWith* 方法,这些方法将适用于 Artifact 基数和 FileSystemLocation 类型的其他组合。

如需详细了解如何扩展 AGP,建议您阅读 Gradle 构建系统手册中的以下几个部分: