Gradle 与 AGP 构建 API: 进一步完善您的插件!
欢迎阅读 MAD Skills 系列 之 Gradle 与 AGP 构建 API 的第三篇文章。在上一篇文章《Gradle 与 AGP 构建 API: 如何编写插件》中,您学习了如何编写您自己的插件,以及如何使用 Variants API。
如果您更喜欢通过视频了解此内容,请 点击此处 查看。
在本文中,您将会学习 Gradle 的 Task、Provider、Property 以及使用 Task 进行输入与输出。同时您也将进一步完善您的插件,并学习如何使用新的 Artifact API 访问各种构建产物。
Property
假设我想要创建一个插件,该插件可以使用 Git 版本自动更新应用清单文件中指定的版本号。为了达到这一目标,我需要为构建添加两个 Task。第一个 Task 会获取 Git 版本,而第二个 Task 将会使用该 Git 版本来更新清单文件。
让我们从创建名为 GitVersionTask
的新任务开始。GitVersionTask 需要继承 DefaultTask,同时实现带有注解的 taskAction 函数。下面是查询 Git 树顶端信息的代码。
abstract class GitVersionTask: DefaultTask() { @TaskAction fun taskAction(){ // 这里是获取树版本顶端的代码 val process = ProcessBuilder( "git", "rev-parse --short HEAD" ).start() val error = process.errorStream.readBytes().toString() if (error.isNotBlank()) { System.err.println("Git error : $error") } var gitVersion = process.inputStream.readBytes().toString() //... } }
我不能直接缓存版本信息,因为我想将它存储在一个中间文件中,从而让其他 Task 也可以读取和使用这个值。为此,我需要使用 RegularFileProperty。Property 可以用于 Task 的输入与输出。在本例中,Property 将会作为呈现 Task 输出的容器。我创建了一个 RegularFileProperty,并使用 @get:OutputFile 对其进行注解。OutputFile 是附加至 getter 函数的标记注解。此注解会将 Property 标记为该 Task 的输出文件。
@get:OutputFile abstract val gitVersionOutputFile: RegularFileProperty
现在,我已经声明了 Task 的输出,让我们回到 taskAction() 函数,我会在这里访问文件并写入我想要存储的文本。本例中,我会存储 Git 版本,也就是 Task 的输出。为了简化示例,我将查询 Git 版本的代码替换为了硬编码字符串。
abstract class GitVersionTask: DefaultTask() { @get:OutputFile abstract val gitVersionOutputFile: RegularFileProperty @TaskAction fun taskAction() { gitVersionOutputFile.get().asFile.writeText("1234") } }
现在,Task 已经准备就绪,让我们在插件代码中对其进行注册。首先,我会创建一个名为 ExamplePlugin
的新插件类,并在其中实现 Plugin。如果您不熟悉在 buildSrc 文件夹中创建插件的流程,可以回顾本系列的前两篇文章:《Gradle 与 AGP 构建 API: 配置您的构建文件》、《Gradle 与 AGP 构建 API: 如何编写插件》。
△ buildSrc 文件夹
接下来我会注册 GitVersionTask
并将文件 Property
设置为输出到 build 文件夹中的一个中间文件上。我同时还将 upToDateWhen
设置为 false
,这样此 Task 前一次执行的输出就不会被复用。这也意味着由于该 Task 不会处于最新的状态,因此每次构建时都会被执行。
override fun apply(project: Project) { project.tasks.register( "gitVersionProvider", GitVersionTask::class.java ) { it.gitVersionOutputFile.set( File( project.buildDir, "intermediates/gitVersionProvider/output" ) ) it.outputs.upToDateWhen { false } } }
在 Task 执行完毕后,我就可以检查位于 build/intermediates
文件夹下的 output
文件了。我只要验证 Task 是否存储了我所硬编码的值即可。
接下来让我们转向第二个 Task,该 Task 会更新清单文件中的版本信息。我将它命名为 ManifestTransformTask
,并使用两个 RegularFileProperty
对象作为它的输入值。
abstract class ManifestTransformerTask: DefaultTask() { @get:InputFile abstract val gitInfoFile: RegularFileProperty @get:InputFile abstract val mergedManifest: RegularFileProperty }
我会用第一个 RegularFileProperty 读取 GitVersionTask 生成的输出文件中的内容;用第二个 RegularFileProperty 读取应用的清单文件。然后我就可以用 gitInfoFile 文件中 gitVersion 变量所存储的版本号替换清单文件中的版本号了。
@TaskAction fun taskAction() { val gitVersion = gitInfoFile.get().asFile.readText() var manifest = mergedManifest.asFile.get().readText() manifest = manifest.replace( "android:versionCode=\"1\"", "android:versionCode=\"${gitVersion}\"" ) }
现在,我可以写入更新后的清单文件了。首先,我会为输出创建另一个 RegularFileProperty
,并使用 @get:OutputFile
对其进行注解。
@get:OutputFile abstract val updatedManifest: RegularFileProperty
注意: 我本可以使用 VariantOutput 直接设置 versionCode,而无需重写清单文件。但是为了向您展示如何使用构建产物转换,我会通过本示例的方式得到相同的效果。
让我们回到插件,并将一切联系起来。我首先获得 AndroidComponentsExtension。我希望在 AGP 决定创建哪个变体后、在各种对象的值被锁定而无法被修改之前执行这一新 Task。onVariants()
回调会在 beforeVariants()
回调后调用,后者可能会让您想起 前一篇文章。
val androidComponents = project.extensions.getByType( AndroidComponentsExtension::class.java ) androidComponents.onVariants { variant -> //... }
Provider
您可以使用 Provider 连接 Property
到其他需要执行耗时操作 (例如读取文件或网络等外部输入) 的 Task。
我会从注册 ManifestTransformerTask
开始。此 Task 依赖 gitVersionOutput
文件,而该文件是前一个 Task 的输出。我将通过使用 Provider
来访问这一 Property
。
val manifestUpdater: TaskProvider = project.tasks.register( variant.name + "ManifestUpdater", ManifestTransformerTask::class.java ) { it.gitInfoFile.set( //... ) }
Provider 可以用于访问指定类型的值,您可以直接使用 get() 函数,也可以使用操作符函数 (如 map() 和 flatMap()) 将值转换为新的 Provider
。在我回顾 Property
接口时,发现其实现了 Property
接口。您可以将值惰性地设置给 Property
,并在稍候惰性地使用 Provider
访问这些值。
当我查看 register() 的返回类型时,发现它返回了给定类型的 TaskProvider。我将其赋值给了一个新的 val
。
val gitVersionProvider = project.tasks.register( "gitVersionProvider", GitVersionTask::class.java ) { it.gitVersionOutputFile.set( File( project.buildDir, "intermediates/gitVersionProvider/output" ) ) it.outputs.upToDateWhen { false } }
现在我们回过头来设置 ManifestTransformerTask
的输入。在我尝试将来自 Provider
的值映射为输入 Property
时,产生了一个错误。map()
的 lambda 参数接收某种类型 (如 T
) 的值,该函数会产生另一个类型 (如 S
) 的值。
△ 使用 map() 时造成的错误
然而,在本例中,set 函数需要 Provider
类型。我可以使用 flatMap()
函数,该函数也接收一个 T 类型的值,但会产生一个 S
类型的 Provider
,而不是直接产生 S
类型的值。
it.gitInfoFile.set( gitVersionProvider.flatMap( GitVersionTask::gitVersionOutputFile ) )
转换
接下来,我需要告诉变体的产物使用 manifestUpdater
,同时将清单文件作为输入,将更新后的清单文件作为输出。最后,我调用 toTransform() 函数转换单个产物的类型。
variant.artifacts.use(manifestUpdater) .wiredWithFiles( ManifestTransformerTask::mergedManifest, ManifestTransformerTask::updatedManifest ).toTransform(SingleArtifact.MERGED_MANIFEST)
在运行此 Task 时,我可以看到应用清单文件中的版本号被更新成了 gitVersion
文件中的值。需要注意的是,我并没有显式地要求 GitProviderTask
运行。该任务之所以被执行,是因为其输出是 ManifestTransformerTask
的输入,而后者是我所请求运行的。
BuiltArtifactsLoader
让我们添加另一个 Task,来了解如何访问已被更新的清单文件并验证它是否被更新成功。我会创建一个名为 VerifyManifestTask
的新任务。为了读取清单文件,我需要访问 APK 文件,该文件是构建 Task 的产物。为此,我需要将构建 APK 文件夹作为 Task 的输入。
注意,这次我使用了 DirectoryProperty 而不是 FileProperty,因为 SingleArticfact.APK 对象可以表示构建之后存放 APK 文件的目录。
我还需要一个类型为 BuiltArtifactsLoader 的 Property 作为 Task 的第二个输入,我会用它从元数据文件中加载 BuiltArtifacts 对象。元数据文件描述了 APK 目录下的文件信息。若您的项目包含原生组件、多种语言等要素,那么每次构建都可以产生数个 APK。BuiltArtifactsLoader 抽象了识别每个 APK 及其属性 (如 ABI 和语言) 的过程。
@get:Internal abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
是时候实现 Task 了。首先我加载了 buildArtifacts,并保证其中只包含了一个 APK,接着将此 APK 作为 File 实例进行加载。
val builtArtifacts = builtArtifactsLoader.get().load( apkFolder.get() )?: throw RuntimeException("Cannot load APKs") if (builtArtifacts.elements.size != 1) throw RuntimeException("Expected one APK !") val apk = File(builtArtifacts.elements.single().outputFile).toPath()
这时,我已经可以访问 APK 中的清单文件并验证版本是否已经更新成功。为了保持示例的简洁,我在这里只会检查 APK 是否存在。我还添加了一个 "在此处检查清单文件" 的提醒,并打印了成功的信息。
println("Insert code to verify manifest file in ${apk}") println("SUCCESS")
现在我们回到插件的代码以注册此 Task。在插件代码中,我将此 Task 注册为 "Verifier
",并传入 APK 文件夹和当前变体产物的 buildArtifactLoader
对象。
project.tasks.register( variant.name + "Verifier", VerifyManifestTask::class.java ) { it.apkFolder.set(variant.artifacts.get(SingleArtifact.APK)) it.builtArtifactsLoader.set( variant.artifacts.getBuiltArtifactsLoader() ) }
当我再次运行 Task 时,可以看到新的 Task 加载了 APK 并打印了成功信息。注意,这次我依旧没有显式请求清单转换的执行,但是因为 VerifierTask
请求了最终版本的清单产物,所以自动进行了转换。
总结
我的 插件 中包含三个 Task: 首先,插件会检查当前 Git 树,并将版本存储在一个中间文件中;随后,插件会惰性使用上一步的输出,并使用一个 Provider 将版本号更新至当前的清单文件;最后,插件会使用另一个 Task 访问构建产物,并检查清单文件是否正确更新。
以上就是全部内容!从 7.0 版开始,Android Gradle 插件提供了官方的扩展点,以便您编写自己的插件。使用这些新 API,您可以控制构建输入、读取、修改甚至替换中间和最终产物。
如需了解更多内容,学习如何保持您构建的高效性,请查阅 官方文档 和 gradle-recipes。
欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Gitee Go代码格式审查、程序编译和冒烟测试 | CI/CD搭建流程-Gitee篇
本文分享自中移OneOS微信公众号:《CI/CD搭建流程-Gitee篇》,作者: Kisann。 Gitee CI/CD能力 Gitee,即码云,是OSCHINA.NET推出的代码托管平台,已有超过600 万的开发者选择Gitee。Gitee Go是Gitee推出的CI/CD(持续构建与集成)服务,类似GitLab CI/CD,用户可以通过自定义构建流程,实现构建集成自动化。Gitee Go目前已支持Maven、Gradle、npm、Python、Ant、PHP、Golang 等工具和语言的持续构建与集成能力。Gitee Go也支持脚本模式,即流水线文件中描述执行脚本,定义任何想做的事情,比如代码格式审查等。由于目前只支持添加Linux主机,因此脚本语言选择shell。 让Gitee Go做什么 嵌入式软件开发领域高频使用的开发语言是C语言,在大型项目中,我们往往会有格式审查、编译审查、冒烟测试等需求。如果能在代码托管平台进行格式规范审查和编译问题审查,将会大幅提高合入代码的规范性,把控新代码对原有代码的影响,提高代码整体质量。 本文将手把手教学如何让Gitee Go帮我们做代码格式审...
- 下一篇
Bootstrap Blazor 更新版本 6.2.0
Bootstrap Blazor 是一款基于 Bootstrap 的 企业级 Blazor UI 组件库,目前内置近100 个组件,欢迎大家尝试使用。 破坏性更新 refactor(#I4P0MT): 表单内组件前置标签由原来的默认四个汉字宽度更改为六个汉字宽度#I4P0MT refactor(#I4OZ32): 组件Tab与Layout移除TabItemTextDictionary参数#I4OZ32 改用页面级标签TabItemOptionAttribute feat(#I4OTDY): 移除NavigateTo扩展方法#I4OTDY 由于用此扩展方法生成的TabItem无法保持标签页状态(丢失Text)等属性,改用页面内使用TabItemOptionAttribute属性替换 feat(#I4NAQ4): 组件BootstrapDynamicComponent参数集合更改为IDictionary<string, object?>#I4NAN8 方便使用者赋值避免触发不可为空检查绿色波浪线 feat(#I4NAN8): 组件ModalDialog内置一个保存按钮默认不显示...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- 设置Eclipse缩进为4个空格,增强代码规范