关于android:Gradle-与-AGP-构建-API-进一步完善您的插件

58次阅读

共计 6268 个字符,预计需要花费 16 分钟才能阅读完成。

欢送浏览 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。

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

正文完
 0