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

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

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

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 ,以及构建脚本中定义的产品变种。


  • Variant

    https://developer.android.google.cn/studio/build/build-variants


在您的构建文件中,使用声明式 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 仓库 :
https://github.com/android/gradle-recipes
import com.android.build.api.variant.ApplicationAndroidComponentsExtensionimport org.gradle.api.Pluginimport 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 中读取和转换产物。

您也可以通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!



推荐阅读

如页面未加载,请刷新重试



 点击屏末  | 即刻了解扩展 Android Gradle 插件更多内容




登录查看更多
0

相关内容

应用程序接口(简称 API),又称为应用编程接口,就是软件系统不同组成部分衔接的约定。
专知会员服务
91+阅读 · 2020年12月26日
【2020新书】现代C++初学者指南,301页pdf
专知会员服务
159+阅读 · 2020年7月24日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
69+阅读 · 2020年1月17日
【GitHub实战】Pytorch实现的小样本逼真的视频到视频转换
专知会员服务
35+阅读 · 2019年12月15日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
22+阅读 · 2019年11月7日
实战 | 使用 Kotlin Flow 构建数据流 "管道"
谷歌开发者
0+阅读 · 2022年3月30日
Android Studio 新特性详解
谷歌开发者
0+阅读 · 2022年1月19日
在 Android 12 中构建更现代的应用 Widget
谷歌开发者
0+阅读 · 2022年1月12日
Gradle 与 AGP 构建 API: 进一步完善您的插件!
谷歌开发者
0+阅读 · 2022年1月5日
如何在golang代码里面解析容器镜像
阿里技术
0+阅读 · 2022年1月5日
React Native 新架构是如何工作的?
InfoQ
0+阅读 · 2021年12月31日
Flutter 之美 | 开发者说·DTalk
谷歌开发者
1+阅读 · 2021年12月23日
Gradle 与 AGP 构建 API: 配置您的构建文件
谷歌开发者
0+阅读 · 2021年12月21日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
1+阅读 · 2014年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2010年12月31日
国家自然科学基金
0+阅读 · 2008年12月31日
Arxiv
92+阅读 · 2020年2月28日
A Comprehensive Survey on Transfer Learning
Arxiv
121+阅读 · 2019年11月7日
VIP会员
相关资讯
实战 | 使用 Kotlin Flow 构建数据流 "管道"
谷歌开发者
0+阅读 · 2022年3月30日
Android Studio 新特性详解
谷歌开发者
0+阅读 · 2022年1月19日
在 Android 12 中构建更现代的应用 Widget
谷歌开发者
0+阅读 · 2022年1月12日
Gradle 与 AGP 构建 API: 进一步完善您的插件!
谷歌开发者
0+阅读 · 2022年1月5日
如何在golang代码里面解析容器镜像
阿里技术
0+阅读 · 2022年1月5日
React Native 新架构是如何工作的?
InfoQ
0+阅读 · 2021年12月31日
Flutter 之美 | 开发者说·DTalk
谷歌开发者
1+阅读 · 2021年12月23日
Gradle 与 AGP 构建 API: 配置您的构建文件
谷歌开发者
0+阅读 · 2021年12月21日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
相关基金
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
1+阅读 · 2014年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2010年12月31日
国家自然科学基金
0+阅读 · 2008年12月31日
Top
微信扫码咨询专知VIP会员