编写 Gradle 插件

Android Gradle 插件 (AGP) 是 Android 应用程序的官方构建系统。它包含对编译许多不同类型的源代码以及将它们链接在一起以形成可以在物理 Android 设备或模拟器上运行的应用程序的支持。

AGP 包含用于插件的扩展点,以控制构建输入并通过可以与标准构建任务集成的全新步骤扩展其功能。以前版本的 AGP 没有明确与内部实现分离的官方 API。从 7.0 版开始,AGP 具有一组 官方稳定 API,您可以依赖这些 API。

AGP API 生命周期

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

  • 内部:不供公共使用
  • 孵化:可供公众使用,但尚未最终确定,这意味着它们可能与最终版本不兼容
  • 公共:可供公众使用且稳定
  • 已弃用:不再受支持,并已由新的 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 表达式 并生成一个 Provider 类型为 S 的值,即 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> 值的来源。在这种情况下,仅当调用 Property.get() 时才实例化 T 值。
TaskProvider
实现 Provider<Task>。要生成 TaskProvider,请使用 tasks.register() 而不是 tasks.create(),以确保仅在需要时才惰性实例化任务。您可以使用 flatMap() 在创建 Task 之前访问 Task 的输出,这在您想要将输出用作其他 Task 实例的输入时很有用。

Provider 及其转换方法对于以惰性方式设置任务的输入和输出至关重要,也就是说,无需预先创建所有任务并解析值。

Provider 还包含任务依赖项信息。当您通过转换 Task 输出来创建 Provider 时,该 Task 成为 Provider 的隐式依赖项,并且将在 Provider 的值被解析时创建并运行,例如当另一个 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 以及对其任务输出(或构建工件)的访问,您可以读取和转换这些输出。有关更多信息,请参阅本文档中的 Variant API、工件和任务 部分。

Gradle 构建阶段

构建项目本质上是一个复杂且资源密集的过程,并且存在各种功能,例如任务配置避免、最新检查和配置缓存功能,有助于最大限度地减少在可重复或不必要的计算上花费的时间。

为了应用其中一些优化,Gradle 脚本和插件必须在每个不同的 Gradle 构建阶段(初始化、配置和执行)遵守严格的规则。在本指南中,我们将重点介绍配置和执行阶段。您可以在 Gradle 构建生命周期指南 中找到有关所有阶段的更多信息。

配置阶段

在配置阶段,将评估构建中所有项目的构建脚本,应用插件,并解析构建依赖项。此阶段应用于使用 DSL 对象配置构建,以及惰性注册任务及其输入。

由于配置阶段始终运行,无论请求运行哪个任务,因此保持配置阶段简洁并限制任何计算依赖于除构建脚本本身之外的输入尤为重要。也就是说,您不应该执行外部程序或从网络读取,也不应该执行可以延迟到执行阶段作为适当的 Task 实例的长时间计算。

执行阶段

在执行阶段,将执行请求的任务及其依赖任务。具体而言,将执行标记有 @TaskActionTask 类方法。在任务执行期间,您可以从输入(例如文件)中读取并通过调用 Provider<T>.get() 来解析惰性提供者。以这种方式解析惰性提供者会启动一系列 map()flatMap() 调用,这些调用遵循提供者中包含的任务依赖项信息。惰性运行任务以实例化所需的值。

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 实例。

  1. DSL 解析:这是评估构建脚本以及创建和设置 android 块中来自 Android DSL 对象的各种属性的时间。以下部分中描述的 Variant API 回调也在此阶段注册。
  2. finalizeDsl():回调,允许您在锁定 DSL 对象以创建组件(变体)之前更改这些对象。VariantBuilder 对象是根据 DSL 对象中包含的数据创建的。

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

  4. beforeVariants():此回调可以通过 VariantBuilder 影响创建哪些组件以及它们的一些属性。它仍然允许修改构建流程和生成的工件。

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

  6. onVariants():在此回调中,您可以访问创建的 Variant 对象,并且可以为其包含的 Property 值设置值或提供者,以便惰性计算。

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

  8. 任务创建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,您可以通过 selector() 方法从 androidComponentsExtension 上获取。您可以使用它根据组件的名称、构建类型或产品风味来过滤参与回调调用的组件。

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() })
}

将生成的源代码贡献给构建

您的插件可以贡献几种类型的生成的源代码,例如

有关您可以添加的源代码的完整列表,请参阅 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 还包含一个扩展机制,允许您读取或转换构建过程中产生的中间和最终工件。例如,您可以读取最终合并的 AndroidManifest.xml 文件内容,以便在自定义的 Task 中对其进行分析,或者您可以将其内容完全替换为由您的自定义 Task 生成的清单文件的内容。

您可以在 Artifact 类的参考文档中找到当前支持的工件列表。每种工件类型都有一些有用的属性。

基数

Artifact 的基数代表其 FileSystemLocation 实例的数量,或工件类型文件的数量或目录数量。您可以通过检查其父类来获取有关工件基数的信息:具有单个 FileSystemLocation 的工件将是 Artifact.Single 的子类;具有多个 FileSystemLocation 实例的工件将是 Artifact.Multiple 的子类。

FileSystemLocation 类型

您可以通过查看其参数化的 FileSystemLocation 类型来检查 Artifact 是否代表文件或目录,该类型可以是 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_MANIFEST 是一个 SingleArtifact,它是一个 RegularFile。因此,我们需要使用 wiredWithFiles 方法,该方法接受输入的单个 RegularFileProperty 引用和输出的单个 RegularFilePropertyTaskBasedOperation 类上还有其他 wiredWith* 方法,这些方法适用于 Artifact 基数和 FileSystemLocation 类型的其他组合。

要详细了解如何扩展 AGP,我们建议您阅读 Gradle 构建系统手册中的以下部分