前语
咱们在项目中常常需求扩展AGP
插件,比方重命名APK
,校验Manifest
文件,对项目中的图片做统一压缩等操作。
总得来说,咱们需求对AGP
插件编译过程中的中心产品(即Artifact
)做一些操作,但是老版的AGP
并没有供给相关的API
,导致咱们需求去查看相关的代码的详细完结,并经过反射等手法获取对应产品。
本文主要介绍旧版AGP
插件在扩展方面存在的问题,并介绍新版Variant API
与Artifact API
的运用,经过简练高雅的办法扩展AGP
插件
本文主要内容是对《社区说:扩展 Android 构建流程 – 根据新版 Variant/Artifact APIs》视频学习的输出,感兴趣的同学能够直接查看视频,链接在文末
现在存在的问题
内部完结没有与API
分离
旧版 AGP
没有与内部完结明确分隔的官方 API
,导致用户运用时常常依靠了内部的详细完结,在开发AGP
插件时,咱们常常直接依靠于com.android.tools.build:gradle:x.x.x
。
为此从 7.0 版别开始,AGP
将供给一组安稳的官方 API
,即com.android.tools.build:gradle-api:x.x.x
。理论上说,在编写插件时,现在主张仅依靠 gradle-api
工件,以便仅运用揭露的接口和类。
开发插件时依靠Task
的详细完结
因为旧版AGP
没有供给对应的API
获取相应产品,咱们一般需求先了解是哪个Task
处理了相关的资源,然后查看该Task
的详细完结,经过相关API
或许反射手法获取对应的产品。
比方咱们要开发一个校验Manifest
文件的插件,在旧版AGP
中需求经过以下办法处理:
def processManifestTask: ManifestProcessorTask = project.tasks.getByName("process${variant.name.capitalize()}Manifest")
processManifestTask.doLast {
logger.warn("main manifest: " + taskCompat.mainManifest)
logger.warn("manifestOutputDirectory: " + taskCompat.manifestOutputDirectory)
//...
}
}
这种写法主要有几个问题:
- 成本较高,开发时需求了解相应
Task
的详细完结 -
AGP
晋级时相应Task
的完结常常会发生变化,因而每次晋级都需求做一系列的适配作业
总得来说,体系会将 AGP
创立的使命视为完结细节,不会作为公共 API
揭露。您有必要避免测验获取 Task
目标的实例或猜想 Task
名称,以及直接向这些 Task
目标添加回调或依靠项。
Variant API
介绍
关于Variant API
,咱们首要了解一下什么是Variant
?
Variant
官网翻译为变体,看起来不太好了解。其实Variant
就是buildType
与Flavor
的组合,如下所示,2个buildType
与2个Flavor
能够组合出4个Variant
Variant API
是 AGP
中的扩展机制,可让您操作build.gradle
中的各种装备。您还能够经过 Variant API
访问构建期间创立的中心产品和终究产品,例如类文件、兼并后的Manifest
或 APK/AAB
文件。
Android
构建流程和扩展点
AGP
主要经过以下几步来创立和履行其 Task
实例
-
DSL
解析:发生在体系评估build
脚本时,以及创立和设置android
代码块中的Android DSL
目标的各种特点时。后面几部分中介绍的Variant API
回调也是在此阶段注册的。 -
finalizeDsl()
:您能够经过此回调在DSL
目标因组件(变体)创立而被确定之前对其进行更改。VariantBuilder
目标是根据DSL
目标中包含的数据创立的。 -
DSL
确定:DSL
现已被确定,无法再进行更改。 -
beforeVariants()
:此回调可经过VariantBuilder
影响体系会创立哪些组件以及所创立组件的部分特点。它还支撑对build
流程和生成的工件进行修正。 - 变体创立:将要创立的组件和工件列表现已最后确定,无法更改。
-
onVariants()
:在此回调中,您能够访问已创立的Variant
目标,您还能够为它们包含的Property
值设置值或Provider
,以进行延迟计算。 - 变体确定:变体目标现已被确定,无法再进行更改。
- 使命已创立:运用
Variant
目标及其Property
值创立履行build
所必需的Task
实例。
总得来说,为咱们供给了3个回调办法,它们各自的运用场景如上图所示,比较老版别的Variant API
,新版别的生命周期区分的愈加明晰详尽
Artifact API
介绍
Artifact
即产品,Artifact API
即咱们获取中心产品或许终究产品的API
,经过Artifact API
咱们能够对中心产品进行增修正查操作而不必关心详细的完结
每个Artifact
类都能够完结以下任一接口,以表明自己支撑哪些操作:
-
Transformable
:答应将Artifact
作为输入,供在该接口上履行任意转换并输出新版Artifact
的Task
运用。 -
Appendable
:仅适用于作为Artifact.Multiple
子类的工件。它意味着能够向Artifact
附加内容,也就是说,自定义Task
能够创立此Artifact
类型的新实例,这些实例将添加到现有列表中。 -
Replaceable
:仅适用于作为Artifact.Single
子类的工件。可替换的Artifact
能够被作为Task
输出生成的全新实例替换。
除了支撑上述三种工件修正操作之外,每个工件还支撑 get()
(或 getAll()
)操作;该操作会返回 Provider
以及该产品的终究版别(在对产品的一切操作均完结之后)。
Get
操作
Get
操作能够获得产品的终究版别(在对产品的一切操作完结之后),比方你想要查看merged manifest
文件,能够经过以下办法完结
abstract class VerifyManifestTask: DefaultTask() {
@get:InputFile
abstract val mergedManifest: RegularFileProperty
@TaskAction
fun taskAction() {
val mergedManifestFile = mergedManifest.get().asFile
// ... verify manifest file content ...
}
}
androidComponents {
onVariants {
// 注册Task
project.tasks.register<VerifyManifestTask>("${name}VerifyManifest") {
// 获取merged_manifest并给Task设值
mergedManifest.set(artifacts.get(ArtifactType.MERGED_MANIFEST))
}
}
}
如上所示,经过以上办法,不需求知道依靠于哪个Task
,也不需求了解Task
的详细完结,就能够轻松获取MERGED_MANIFEST
的内容
Transformation
操作
Transformation
即改换操作,它能够把原有产品作为输入值,进行改换后将结果作为输出值,并传递给下一个改换
下面咱们来看一个将Manifest
的VersionCode
替换为git head
的改换示例:
// 获取git head的Task
abstract class GitVersionTask: DefaultTask() {
@get:OutputFile
abstract val gitVersionOutputFile: RegularFileProperty
@TaskAction
fun taskAction() {
val process = ProcessBuilder("git", "rev-parse --short HEAD").start()
val error = process.errorStream.readBytes().decodeToString()
if (error.isNotBlank()) {
throw RuntimeException("Git error : ${'$'}error")
}
var gitVersion = process.inputStream.readBytes().decodeToString()
gitVersionOutputFile.get().asFile.writeText(gitVersion)
}
}
// 修正Manifest的Task
abstract class ManifestTransformerTask: DefaultTask() {
@get:InputFile
abstract val gitInfoFile: RegularFileProperty
@get:InputFile
abstract val mergedManifest: RegularFileProperty
@get:OutputFile
abstract val updatedManifest: RegularFileProperty
@TaskAction
fun taskAction() {
val gitVersion = gitInfoFile.get().asFile.readText()
var manifest = mergedManifest.get().asFile.readText()
manifest = manifest.replace(
"android:versionCode=\"1\"",
"android:versionCode=\"${gitVersion}\"")
updatedManifest.get().asFile.writeText(manifest)
}
}
// 注册Task
androidComponents {
onVariants {
// 创立git Version Task Provider
val gitVersion = tasks.register<GitVersionTask>("gitVersion") {
gitVersionOutputFile.set(
File(project.buildDir, "intermediates/git/output"))
}
// 创立修正Manifest的Task
val manifestUpdater = tasks.register<ManifestTransformerTask>("${name}ManifestUpdater") {
// 把GitVersionTask的结果设置给gitInfoFile
gitInfoFile.set(
gitVersion.flatMap(GitVersionTask::gitVersionOutputFile)
)
}
// manifestUpdater Task 与 AGP 进行衔接
artifacts.use(manifestUpdater)
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest)
.toTransform(ArtifactType.MERGED_MANIFEST)
}
}
经过以上操作,就能够把git head
的值替换Manifest
中的内容,能够注意到manifestUpdater
依靠于gitVersion
,但咱们却没有写dependsOn
相关的逻辑。
这是因为它们都是TaskProvider
类型,TaskPrOVIDER
还带着有使命依靠项信息。当您经过flatmap
一个 Task
的输出来创立 Provider
时,该 Task
会成为相应Provider
的隐式依靠项,不管 Provider
的值在何时进行解析(例如当另一个 Task
需求它时),体系都要会创立并运行该 Task
。
同理,咱们也主动隐式依靠了process${variant.name.capitalize()}Manifest
这个Task
Append
操作
Append
仅与运用 MultipleArtifact
润饰的产品类型相关。 因为此类类型表示为 Directory
或 RegularFile
的列表,因而使命能够声明将附加到列表的输出。
// 声明Task
abstract class SomeProducer: DefaultTask() {
@get:OutputFile
abstract val output: RegularFileProperty
@TaskAction
fun taskAction() {
val outputFile = output.get().asFile
// … write file content …
}
}
// 注册Task
androidComponents {
onVariants {
val someProducer = project.tasks.register<SomeProducer>("${name}SomeProducer") {
// ... configure your task as needed ...
}
artifacts.use(someProducer)
.wiredWith(SomeProducerTask::output)
.toAppendTo(ArtifactType.MANY_ARTIFACT)
}
}
Creation
操作
此操效果一个新的 Artifact
替换当时的Artifact
,丢弃一切曾经的Artifact
。这是一个“输出”操作,其中使命声明自己是产品的唯一供给者。如果有多个使命将自己声明为产品供给者,则最后一个将获胜。
例如,自定义使命或许不运用内置清单兼并,而是经过代码写入一个新的。
// 声明Task
abstract class ManifestFileProducer: DefaultTask() {
@get:OutputFile
abstract val outputManifest: RegularFileProperty
@TaskAction
fun taskAction() {
val mergedManifestFile = outputManifest.get().asFile
// ... write manifest file content ...
}
}
// 注册Task
androidComponents {
onVariants {
val manifestProducer = project.tasks.register<ManifestProducerTask>("${name}ManifestProducer") {
//… configure your task as needed ...
}
artifacts.use(manifestProducer)
.wiredWith(ManifestProducerTask::outputManifest)
.toCreate(ArtifactType.MERGED_MANIFEST)
}
}
Artifact API
的问题
Artifact API
现在的问题就在于支撑的产品类型还有限,只要以下几种
SingleArtifact.APK
SingleArtifact.MERGED_MANIFEST
SingleArtifact.OBFUSCATION_MAPPING_FILE
SingleArtifact.BUNDLE
SingleArtifact.AAR
SingleArtifact.PUBLIC_ANDROID_RESOURCES_LIST
SingleArtifact.METADATA_LIBRARY_DEPENDENCIES_REPORT
MultipleArtifact.MULTIDEX_KEEP_PROGUARD
MultipleArtifact.ALL_CLASSES_DIRS
MultipleArtifact.ALL_CLASSES_JARS
MultipleArtifact.ASSETS
还有很多常用的中心产品如MERGED_RES
,MERGED_NATIVE_LIBS
等都还不支撑,因而在现阶段还是不可避免的运用老版别的API
来获取这些资源
总结
新版Variant API
经过专心于产品而不是Task
,自定义插件或构建脚本能够安全地扩展 AGP
插件,而不受构建流程更改或Task
完结等内部细节的支配。开发者不需求知道要修正的产品依靠于哪个Task
,也不需求知道Task
的详细完结,能够有效降低咱们开发与晋级Gradle
插件的成本。
虽然现在新版Variant API
支撑的获取的中心产品类型还有限,但这也应该能够确定是AGP
插件扩展将来的方向了。在AGP8.0
中,旧版 Variant API
将被废弃,而在AGP9.0
中,旧版Vaiant API
将被删去,并将移除对私有内部 AGP
类的访问权限,因而现在应该是时分了解一下新版Variant API
的运用了~
示例代码
本文一切代码可见:github.com/android/gra…
参考资料
New APIs in the Android Gradle Plugin
社区说|扩展 Android 构建流程 – 根据新版 Variant/Artifact APIs
扩展 Android Gradle 插件