美文网首页Gradle
Gradle 与 AGP 构建 API: 如何编写插件

Gradle 与 AGP 构建 API: 如何编写插件

作者: 谷歌开发者 | 来源:发表于2021-12-24 19:48 被阅读0次
    image

    欢迎阅读 MAD Skills 系列 之 Gradle 与 AGP 构建 API 的第二篇文章。通过上篇文章《Gradle 与 AGP 构建 API: 配置您的构建文件》您已经了解 Gradle 的基础知识以及如何配置 Android Gradle Plugin。在本文中,您将学习如何通过编写您自己的插件来扩展您的构建。如果您更喜欢通过视频了解此内容,请在 此处 查看。

    Android Gradle Plugin 从 7.0 版开始提供稳定的扩展点,用于操作变体配置和生成的构建产物。该 API 的一些部分是最近才完成的,因此我将会在本文中使用 7.1 版 AGP (撰写本文时尚处于 Beta 版)。

    Gradle Task

    我会从一个全新的项目开始。如果您想要同步学习,可以通过选择基础 Activity 模板来创建一个新项目。

    让我们从创建 Task 并打印输出开始——没错,就是 hello world。为此,我会在应用层的 build.gradle.kts 文件注册一个新的 Task,并将其命名为 "hello"

    tasks.register("hello"){ }
    

    现在 Task 已经准备就绪,我们可以打印出 "hello" 并加上项目名称。注意当前 build.gradle.kts 文件属于应用模块,所以 project.name 将会是当前模块的名字 "app"。而如果我是用 project.parent?.name,就会返回项目的名称。

    tasks.register("hello"){
       println("Hello " + project.parent?.name)
    }
    

    是时候运行该 Task 了。此时查看 Task 列表,可以看到我的 Task 已经位列其中。

    △ 新的 Task 已经列在 Android Studio 的 Gradle 窗格中了

    我可以双击 hello Task 或通过终端执行此 Task,并在构建输出中观察它所打印的 hello 信息。

    △ Task 在构建输出中打印的 hello 信息

    在查看日志时,我可以看到此信息是在配置阶段打印的。配置阶段实际上与执行 Task 的功能 (例如本例中的打印 Hello World) 无关。配置阶段是进行 Task 配置以作用于其执行的阶段。您可以在此阶段确定 Task 的输入、参数,以及输出的位置。

    无论请求运行哪个 Task,配置阶段都会执行。在配置阶段执行耗时操作会导致较长的配置时间。

    Task 的执行应当只在执行阶段发生,所以我们需要将打印调用移动至执行阶段。我可以通过添加 doFirst() 或 doLast() 函数来达到这一目的,二者分别可以在执行阶段的开始和结束时打印 hello 消息。

    tasks.register("hello"){
       doLast {
           println("Hello " + project.parent?.name)
       }
    }
    

    当我再次运行 Task 时,我可以看到 hello 信息是在执行阶段打印的。

    △ 现在 Task 会在执行阶段打印 hello 信息

    我的自定义 Task 目前位于 build.gradle.kts 文件中。添加自定义 Task 到 build.gradle 文件是创建自定义构建脚本的方便法门。不过,在我的插件代码变得愈发复杂时,这种方式不利于进行扩展。我们建议将自定义 Task 和插件实现放置于 buildSrc 文件夹。

    在 buildSrc 中实现插件

    在编写更多代码前,让我们将 hello Task 移动至 buildSrc。我会创建一个新的文件夹,并将其命名为 buildSrc。接下来,我为插件项目创建了一个 build.gradle.kts 文件,这样 Gradle 就会自动将此文件夹添加至构建。

    这是项目根文件夹中的顶层目录。注意,我并不需要在我的项目中将其添加为模块。Gradle 会自动编译目录中的代码,并将其加入到您构建脚本的 classpath 中。

    接下来,我创建了一个新的 src 文件夹与一个名为 HelloTask 的类。我将新的类改为 abstract 类,并使其继承 DefaultTask。随后,我会添加一个名为 taskAction 的函数、使用 @TaskAction 注解此函数,并将我自定义的 Task 代码迁移至此函数中。

    abstract class HelloTask: DefaultTask() {   
       @TaskAction
       fun taskAction() {
           println("Hello \"${project.parent?.name}\" from task!")
       }
    }
    

    现在,我的 Task 已经就绪。我会创建一个新的插件类,这需要实现 Plugin 类型并覆盖 apply()函数。Gradle 会调用此函数并传入 Project 对象。为了注册 HelloTask,我需要在 project.tasks 上调用 register(),并为这个新的 Task 命名。

    class CustomPlugin: Plugin<Project> {
       override fun apply(project: Project) {
           project.tasks.register<HelloTask>("hello")
       }
    }
    

    此时,我也可以将我的 Task 声明为依赖其他 Task。

    class CustomPlugin: Plugin<Project> {
       override fun apply(project: Project) {
           project.tasks.register<HelloTask>("hello"){
               dependsOn("build")
           }
       }
    }
    

    下面让我们应用新的插件。注意,如果我的项目含有多个模块,我也可以通过将此插件加入其他 build.gradle 文件来复用它。

    plugins {
       id ("com.android.application")
       id ("org.jetbrains.kotlin.android")
    }
    apply<CustomPlugin>()
    android {
      ...
    }
    

    现在,我会运行 hello Task,并像之前一样观察插件的运行。

    ./gradlew hello

    到目前为止,我已经将我的 Task 移至 buildSrc,让我们更进一步,探索新的 Android Gradle Plugin API。AGP 为其构建产物时的生命周期提供了扩展点。

    在开始学习 Variant API 前,让我们先了解什么是 Variant。变体 (variant) 是您应用可以构建的不同版本。假设除了功能完整的应用,您还希望构建一个演示版的应用或用于调试的内部版本。您还可以针对不同的目标 API 或设备类型。变体由多个构建类型组合而成,例如 debug 与 release,以及构建脚本中定义的产品变种。

    在您的构建文件中,使用声明式 DSL 添加构建类型是完全没有问题的。不过,在代码中以这种方式让您的插件影响构建是不可能的,或者说难以使用声明式语法进行表达。

    AGP 通过解析构建脚本及 android 块中设置的属性来启动构建。新的 Variant API 回调让我可以从 androidComponents 扩展中添加 finalizeDSL() 回调。在此回调中,我可以在 DSL 对象应用于 Variant 创建前对它们进行修改。我将创建一个新的构建类型并且设置它的属性。

    val extension = project.extensions.getByName(
       "androidComponents"
    ) as ApplicationAndroidComponentsExtension
    
    extension.finalizeDsl { ext->
       ext.buildTypes.create("staging").let { buildType ->
           buildType.initWith(ext.buildTypes.getByName("debug"))
           buildType.manifestPlaceholders["hostName"] = "example.com"
           buildType.applicationIdSuffix = ".debugStaging"
       }
    }
    

    注意,在此阶段中,我可以创建或注册新的构建类型并设置它们的属性。在阶段结束时,AGP 将会锁定 DSL 对象,这样它们就无法再被更改。如果我再次运行构建,我会看到应用的 staging 版本被构建了。

    现在,假设我的一个测试没有通过,这时我想要禁用单元测试来构建一个内部版本,以找出问题所在。

    为了禁用单元测试,我可以使用 beforeVariants() 回调。该回调可以让我通过 VariantBuilder 对象进行这类修改。在这里,我会检查当前变体是否是我为 staging 创建的变体。接下来,我将禁用单元测试并设置不同的 minSdk 版本。

    extension.beforeVariants { variantBuilder ->
       if (variantBuilder.name == "staging") {
           variantBuilder.enableUnitTest = false
           variantBuilder.minSdk = 23
       }
    }
    

    在此阶段后,组件列表和将要创建产物都会被确定。

    本示例的完整代码如下。如需更多此类示例,请查阅 Github gradle-recipes 仓库:

    import com.android.build.api.variant.ApplicationAndroidComponentsExtension
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class CustomPlugin: Plugin<Project> {
        override fun apply(project: Project) {
            project.tasks.register("hello"){ task->
                task.doLast {
                    println("Hello " + project.parent?.name)
                }
            }
    
            val extension = project.extensions.getByName("androidComponents") as ApplicationAndroidComponentsExtension
            extension.beforeVariants { variantBuilder ->
                if (variantBuilder.name == "staging") {
                    variantBuilder.enableUnitTest = false
                    variantBuilder.minSdk = 23
                }
            }
            extension.finalizeDsl { ext->
                ext.buildTypes.create("staging").let { buildType ->
                    buildType.initWith(ext.buildTypes.getByName("debug"))
                    buildType.manifestPlaceholders["hostName"] = "internal.example.com"
                    buildType.applicationIdSuffix = ".debugStaging"
                    // 在后面解释 beforeVariants 时添加了本行代码。
                    buildType.isDebuggable = true 
                }
            }
        }
    }
    

    总结

    编写您自己的插件,您可以扩展 Android Gradle Plugin 并根据您的项目需求自定义您的构建!

    在本文中,您已经了解了如何使用新的 Variant API 来在 AndroidComponentsExtension 中注册回调、使用 DSL 对象初始化 Variant、影响已被创建的 Variant,以及在 beforeVariants() 中它们的属性。

    在下一篇文章中,我们将进一步介绍 Artifacts API,并向您展示如何从您的自定义 Task 中读取和转换产物。

    欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!

    相关文章

      网友评论

        本文标题:Gradle 与 AGP 构建 API: 如何编写插件

        本文链接:https://www.haomeiwen.com/subject/yrhrqrtx.html