前语

咱们在项目中常常需求扩展AGP插件,比方重命名APK,校验Manifest文件,对项目中的图片做统一压缩等操作。
总得来说,咱们需求对AGP插件编译过程中的中心产品(即Artifact)做一些操作,但是老版的AGP并没有供给相关的API,导致咱们需求去查看相关的代码的详细完结,并经过反射等手法获取对应产品。

本文主要介绍旧版AGP插件在扩展方面存在的问题,并介绍新版Variant APIArtifact 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)
        //...
    }    
}

这种写法主要有几个问题:

  1. 成本较高,开发时需求了解相应Task的详细完结
  2. AGP晋级时相应Task的完结常常会发生变化,因而每次晋级都需求做一系列的适配作业

总得来说,体系会将 AGP 创立的使命视为完结细节,不会作为公共 API 揭露。您有必要避免测验获取 Task 目标的实例或猜想 Task 名称,以及直接向这些 Task 目标添加回调或依靠项。

Variant API介绍

关于Variant API,咱们首要了解一下什么是Variant?
Variant官网翻译为变体,看起来不太好了解。其实Variant就是buildTypeFlavor的组合,如下所示,2个buildType与2个Flavor能够组合出4个Variant

如何优雅地扩展 AGP 插件?

Variant APIAGP 中的扩展机制,可让您操作build.gradle中的各种装备。您还能够经过 Variant API 访问构建期间创立的中心产品和终究产品,例如类文件、兼并后的ManifestAPK/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 实例。

如何优雅地扩展 AGP 插件?

总得来说,为咱们供给了3个回调办法,它们各自的运用场景如上图所示,比较老版别的Variant API,新版别的生命周期区分的愈加明晰详尽

Artifact API介绍

Artifact即产品,Artifact API即咱们获取中心产品或许终究产品的API,经过Artifact API咱们能够对中心产品进行增修正查操作而不必关心详细的完结

每个Artifact类都能够完结以下任一接口,以表明自己支撑哪些操作:

  • Transformable:答应将 Artifact 作为输入,供在该接口上履行任意转换并输出新版 ArtifactTask 运用。
  • 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即改换操作,它能够把原有产品作为输入值,进行改换后将结果作为输出值,并传递给下一个改换

下面咱们来看一个将ManifestVersionCode替换为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 润饰的产品类型相关。 因为此类类型表示为 DirectoryRegularFile 的列表,因而使命能够声明将附加到列表的输出。

// 声明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_RESMERGED_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 插件