△ Gradle 与 AGP 构建 API: 进一步完善您的插件
在本文中,您将会学习 Gradle 的 Task、Provider、Property 以及使用 Task 进行输入与输出。同时您也将进一步完善您的插件,并学习如何使用新的 Artifact API 访问各种构建产物。
Artifact API
https://developer.android.google.cn/studio/build/extend-agp#variant-api-artifacts-tasks
Property
假设我想要创建一个插件,该插件可以使用 Git 版本自动更新应用清单文件中指定的版本号。为了达到这一目标,我需要为构建添加两个 Task。第一个 Task 会获取 Git 版本,而第二个 Task 将会使用该 Git 版本来更新清单文件。
让我们从创建名为 GitVersionTask 的新任务开始。GitVersionTask 需要继承 DefaultTask,同时实现带有注解的 taskAction 函数。下面是查询 Git 树顶端信息的代码。
abstract class GitVersionTask: DefaultTask() {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()//...}}
abstract val gitVersionOutputFile: RegularFileProperty
RegularFileProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/RegularFileProperty.html
Property
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Property.html
OutputFile
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputFile.html
abstract class GitVersionTask: DefaultTask() {abstract val gitVersionOutputFile: RegularFilePropertyfun taskAction() {gitVersionOutputFile.get().asFile.writeText("1234")}}
现在,Task 已经准备就绪,让我们在插件代码中对其进行注册。首先,我会创建一个名为 ExamplePlugin 的新插件类,并在其中实现 Plugin。如果您不熟悉在 buildSrc 文件夹中创建插件的流程,可以回顾本系列的前两篇文章:《Gradle 与 AGP 构建 API: 配置您的构建文件》、《Gradle 与 AGP 构建 API: 如何编写插件》。
Plugin
https://docs.gradle.org/current/javadoc/org/gradle/api/Plugin.html
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() {abstract val gitInfoFile: RegularFilePropertyabstract val mergedManifest: RegularFileProperty}
fun taskAction() {val gitVersion = gitInfoFile.get().asFile.readText()var manifest = mergedManifest.asFile.get().readText()manifest = manifest.replace("android:versionCode=\"1\"","android:versionCode=\"${gitVersion}\"")}
abstract val updatedManifest: RegularFileProperty
注意 : 我本可以使用 VariantOutput 直接设置 versionCode,而无需重写清单文件。但是为了向您展示如何使用构建产物转换,我会通过本示例的方式得到相同的效果。 https://developer.android.google.cn/reference/tools/gradle-api/7.1/com/android/build/api/variant/VariantOutput?hl=en#versionCode:org.gradle.api.provider.Property
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)androidComponents.onVariants { variant ->//...}
AndroidComponentsExtension
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/extension/AndroidComponentsExtension
Provider
您可以使用 Provider 连接 Property 到其他需要执行耗时操作 (例如读取文件或网络等外部输入) 的 Task。
Provider
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html
我会从注册 ManifestTransformerTask 开始。此 Task 依赖 gitVersionOutput 文件,而该文件是前一个 Task 的输出。我将通过使用 Provider 来访问这一 Property。
val manifestUpdater: TaskProvider = project.tasks.register(variant.name + "ManifestUpdater",ManifestTransformerTask::class.java) {it.gitInfoFile.set(//...)}
get()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#get--
map()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#map-org.gradle.api.Transformer-
flatMap()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#flatMap-org.gradle.api.Transformer-
当我查看 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 }}
register()
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskContainer.html#register-java.lang.String-
TaskProvider
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskProvider.html
现在我们回过头来设置 ManifestTransformerTask 的输入。在我尝试将来自 Provider 的值映射为输入 Property 时,产生了一个错误。map() 的 lambda 参数接收某种类型 (如 T) 的值,该函数会产生另一个类型 (如 S) 的值。
然而,在本例中,set 函数需要 Provider 类型。我可以使用 flatMap() 函数,该函数也接收一个 T 类型的值,但会产生一个 S 类型的 Provider,而不是直接产生 S 类型的值。
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
转换
variant.artifacts.use(manifestUpdater).wiredWithFiles(ManifestTransformerTask::mergedManifest,ManifestTransformerTask::updatedManifest).toTransform(SingleArtifact.MERGED_MANIFEST)
toTransform()
https://developer.android.google.cn/reference/tools/gradle-api/7.1/com/android/build/api/artifact/InAndOutFileOperationRequest#toTransform(com.android.build.api.artifact.InAndOutFileOperationRequest.toTransform.ArtifactTypeT)
BuiltArtifactsLoader
DirectoryProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/DirectoryProperty.html
FileProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/RegularFileProperty.html
SingleArticfact.APK
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/artifact/SingleArtifact.APK
我还需要一个类型为 BuiltArtifactsLoader 的 Property 作为 Task 的第二个输入,我会用它从元数据文件中加载 BuiltArtifacts 对象。元数据文件描述了 APK 目录下的文件信息。若您的项目包含原生组件、多种语言等要素,那么每次构建都可以产生数个 APK。BuiltArtifactsLoader 抽象了识别每个 APK 及其属性 (如 ABI 和语言) 的过程。
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
BuiltArtifactsLoader
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/variant/BuiltArtifactsLoader
BuiltArtifacts
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/variant/BuiltArtifacts
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()
println("Insert code to verify manifest file in ${apk}")println("SUCCESS")
project.tasks.register(variant.name + "Verifier",VerifyManifestTask::class.java) {it.apkFolder.set(variant.artifacts.get(SingleArtifact.APK))it.builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())}
总结
插件
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/ExamplePlugin.kt
首先
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/GitVersion.kt
随后
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/ManifestTransformerTask.kt
最后
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/VerifyManifestTask.kt
官方文档
https://developer.android.google.cn/studio/build/extend-agp
gradle-recipes
https://github.com/android/gradle-recipes
推荐阅读
点击屏末 | 阅读原文 | 即刻了解扩展 Android Gradle 插件更多内容