当前位置:   article > 正文

Transform 被废弃,TransformAction 了解一下~

intentintegrator被废弃了

outside_default.png

前言

Transform API 是 AGP1.5 就引入的特性,主要用于在 Android 构建过程中,在 ClassDex的过程中修改 Class 字节码。利用 Transform API,我们可以拿到所有参与构建的 Class 文件,然后可以借助ASM 等字节码编辑工具进行修改,插入自定义逻辑。

国内很多团队都或多或少的用 AGP 的 Transform API 来搞点儿黑科技,比如无痕埋点,耗时统计,方法替换等。但是在AGP7.0Transform已经被标记为废弃了,并且将在AGP8.0中移除。

Transrom被废弃之后,它的代替品则是Transform Action,它是由Gradle提供的产物变换API

Transform API是由AGP提供的,而Transform Action则是由Gradle提供。不光是 AGP 需要 TransformJava 也需要,所以由 Gradle 来提供统一的 Transform API 也合情合理。

当然如果你只是想利用ASM对字节码插桩,AGP提供了对基于TransformActionASM插桩的封装,只需要使用AsmClassVisitorFactory即可,关于具体的使用可见:Transform 被废弃,ASM 如何适配?

而本文主要包括以下内容:

  1. TransformAction是什么?

  2. 如何自定义TransformAction

  3. TransformActionAGP中的应用

TransformAction是什么?

简单来说,TransformAction就是Gradle提供的产物转换API,可以注册两个属性间的转换Action,将依赖从一个状态切换到另一个状态

我们在项目中的依赖,可能会有多个变体,例如,一个依赖可能有以下两种变体:classes( org.gradle.usage=java-apiorg.gradle.libraryelements=classes )或JAR( org.gradle.usage=java-api, org.gradle.libraryelements=jar)

它们的主要区别就在于,一个的产物是jar,一个则是classes(类目录)

Gradle解析配置时,解析的配置上的属性将确定请求的属性,并选中匹配属性的变体。例如,当配置请求org.gradle.usage=java-api, org.gradle.libraryelements=classes时,就会选择classes目录作为输入。

但是如果依赖项没有所请求属性的变体,那么解析配置就会失败。有时我们可以将依赖项的产物转换为请求的变体。

例如,解压缩JarTranformAction会将 java-api,jars转换为java-api,classes变体。
这种转换可以对依赖的产物进行转换,所以称为“产物转换” 。Gradle允许注册产物转换,并且当依赖项没有所请求的变体时,Gradle将尝试查找一系列产物转换以创建变体。

TransformAction选择和执行逻辑

如上所述,当Gradle解析配置并且配置中的依赖关系不具有带有所请求属性的变体时,Gradle会尝试查找一系列TransformAction以创建变体。

每个注册的转换都是从一组属性转换为一组属性。例如,解压缩转换可以从org.gradle.usage=java-api, org.gradle.libraryelements=jars转换至org.gradle.usage=java-api, org.gradle.libraryelements=classes 。

为了找到一条这样的链,Gradle从请求的属性开始,然后将所有修改某些请求的属性的TransformAction视为通向那里的可能路径。

例如,考虑一个minified属性,它有两个值: truefalse 。minified属性表示是否删除了不必要的类文件。

如果我们的依赖只有minified=false的变体,并且我们的配置中请求了minified=true的属性,如果我们注册了minify的转换,那么它就会被选中

在找到的所有变换链中,Gradle尝试选择最佳的变换链:

  1. 如果只有一个转换链,则选择它。

  2. 如果有两个变换链,并且一个是另一个的后缀,则将其选中。

  3. 如果存在最短的变换链,则将其选中。

  4. 在所有其他情况下,选择将失败并报告错误。

同时还有两个特殊情况:

  1. 当已经存在与请求属性匹配的依赖项变体时,Gradle不会尝试选择产物转换。

  2. artifactType属性是特殊的,因为它仅存在于解析的产物上,而不存在于依赖项上。因此任何只变换artifactTypeTransformAction,只有在使用ArtifactView时才会考虑使用

自定义TransformAction

下面我们就以自定义一个MinifyTransform为例,来看看如何自定义TransformAction,主要用于过滤产物中不必要的文件

定义TransformAction

  1. abstract class Minify : TransformAction<Minify.Parameters> { // (1)
  2. interface Parameters : TransformParameters { // (2) @get:Input
  3. var keepClassesByArtifact: Map<String, Set<String>>
  4. } @get:PathSensitive(PathSensitivity.NAME_ONLY) @get:InputArtifact
  5. abstract val inputArtifact: Provider<FileSystemLocation> override
  6. fun transform(outputs: TransformOutputs) { val fileName = inputArtifact.get().asFile.name for (entry in parameters.keepClassesByArtifact) { // (3)
  7. if (fileName.startsWith(entry.key)) { val nameWithoutExtension = fileName.substring(0, fileName.length - 4)
  8. minify(inputArtifact.get().asFile, entry.value, outputs.file("${nameWithoutExtension}-min.jar")) return
  9. }
  10. }
  11. println("Nothing to minify - using ${fileName} unchanged")
  12. outputs.file(inputArtifact) // (4)
  13. } private fun minify(artifact: File, keepClasses: Set<String>, jarFile: File) {
  14. println("Minifying ${artifact.name}") // Implementation ...
  15. }
  16. }复制代码

代码很简单,主要分为以下几步:

  1. 实现TransformAction接口并声明参数类型

  2. 实现参数接口,实现自定义参数

  3. 获取输入并实现transform逻辑

  4. 输出变换结果,当不需要变换时直接将输入作为变换结果

其实一个TransformAction主要就是输入,输出,变换逻辑三个部分

注册TransformAction

接下来就是注册了,您需要注册TransformAction,并在必要时提供参数,以便在解析依赖项时可以选择它们

  1. val artifactType = Attribute.of("artifactType", String::class.java)val minified = Attribute.of("minified", Boolean::class.javaObjectType)val keepPatterns = mapOf( "guava" to setOf( "com.google.common.base.Optional", "com.google.common.base.AbstractIterator"
  2. )
  3. )
  4. dependencies {
  5. attributesSchema {
  6. attribute(minified) // <1>
  7. }
  8. artifactTypes.getByName("jar") {
  9. attributes.attribute(minified, false) // <2>
  10. }
  11. }
  12. configurations.all {
  13. afterEvaluate { if (isCanBeResolved) {
  14. attributes.attribute(minified, true) // <3>
  15. }
  16. }
  17. }
  18. dependencies { // (4)
  19. implementation("com.google.guava:guava:27.1-jre")
  20. implementation(project(":producer"))
  21. }
  22. dependencies {
  23. registerTransform(Minify::class) { // <5>
  24. from.attribute(minified, false).attribute(artifactType, "jar")
  25. to.attribute(minified, true).attribute(artifactType, "jar")
  26. parameters {
  27. keepClassesByArtifact = keepPatterns // Make sure the transform executes each time
  28. timestamp = System.nanoTime()
  29. }
  30. }
  31. }
  32. tasks.register<Copy>("resolveRuntimeClasspath") {
  33. from(configurations.runtimeClasspath)
  34. into(layout.buildDirectory.dir("runtimeClasspath"))
  35. }复制代码

注册TransformAction也分为以下几步:

  1. 添加minified属性

  2. 将所有JAR文件的minified属性设置为false

  3. 在所有可解析的配置上设置请求的属性为minified=true

  4. 添加将要转换的依赖项

  5. 注册Transformaction,设置fromto的属性,并且传递自定义参数

运行TransformAction

在定义与注册了TransformAction之后,下一步就是运行了

上面我们自定义了resolveRuntimeClasspathTask,Minify转换会在我们请求minified=true的变体时调用

当我们运行gradle resolveRuntimeClasspath时就可以得到如下输出

  1. > Task :resolveRuntimeClasspathNothing to minify - using jsr305-3.0.2.jar unchanged
  2. Minifying guava-27.1-jre.jarNothing to minify - using failureaccess-1.0.1.jar unchangedNothing to minify - using listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar unchangedNothing to minify - using j2objc-annotations-1.1.jar unchangedNothing to minify - using checker-qual-2.5.2.jar unchangedNothing to minify - using error_prone_annotations-2.2.0.jar unchangedNothing to minify - using animal-sniffer-annotations-1.17.jar unchanged复制代码

可以看出,当我们执行task的时候,gradle自动调用了TransformAction,对guava.jar进行了变换,并将结果存储在layout.buildDirectory.dir("runtimeClasspath")

变换ArtifactTypeTransformAction

上文提到,artifactType属性是特殊的,因为它仅存在于解析的产物上,而不存在于依赖项上。因此任何只变换artifactTypeTransformAction,只有在使用ArtifactView时才会考虑使用

其实在AGP中,相当一部分自定义TransformAction都是属于只变换ArtifactType的,下面我们来看下如何自定义一个这样的TransformAction

  1. class TransformActionPlugin : Plugin<Project> {
  2. override fun apply(project: Project) {
  3. project.run {
  4. val artifactType = Attribute.of("artifactType", String::class.java)
  5. dependencies.registerTransform(MyTransform::class.java) { // 1
  6. it.from.attribute(artifactType, "jar")
  7. it.to.attribute(artifactType, "my-custom-type")
  8. }
  9. val myTaskProvider = tasks.register("myTask", MyTask::class.java) {
  10. it.inputCount.set(10)
  11. it.outputFile.set(File("build/myTask/output/file.jar"))
  12. }
  13. val includedConfiguration = configurations.create("includedConfiguration") // 2
  14. dependencies.add(includedConfiguration.name, "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10")
  15. val combinedInputs = project.files(includedConfiguration, myTaskProvider.map { it.outputFile })
  16. val myConfiguration = configurations.create("myConfiguration")
  17. dependencies.add(myConfiguration.name, project.files(project.provider { combinedInputs }))
  18. tasks.register("consumerTask", ConsumerTask::class.java) { // 3
  19. it.artifactCollection = myConfiguration.incoming.artifactView {viewConfiguration ->
  20. viewConfiguration.attributes.attribute(artifactType, "my-custom-type")
  21. }.artifacts
  22. it.outputFile.set(File("build/consumerTask/output/output.txt"))
  23. }
  24. }
  25. }
  26. }复制代码

主要分为以下几步:

  1. 声明与注册自定义Transform,指定输入与输出的artifactType

  2. 创建自定义的 configuration,指定输入的依赖是什么(当然也可以直接用AGP已有的configuration)

  3. 在使用时,通过自定义configurationartifactView,获取对应的产物

  4. ConsumerTask中消费自定义TransformAction的输出产物

然后我们运行./gradlew consumerTask就可以得到以下输出

  1. > Task :app:consumerTask
  2. Processing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.7.10/bac80c520d0a9e3f3673bc2658c6ed02ef45a76a/kotlin-stdlib-common-1.7.10.jar. File exists = trueProcessing ~/AndroidProject/2022/argust/GradleTutorials/app/build/myTask/output/file.jar. File exists = trueProcessing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.7.10/d70d7d2c56371f7aa18f32e984e3e2e998fe9081/kotlin-stdlib-jdk8-1.7.10.jar. File exists = trueProcessing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar. File exists = trueProcessing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/d2abf9e77736acc4450dc4a3f707fa2c10f5099d/kotlin-stdlib-1.7.10.jar. File exists = trueProcessing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.7.10/1ef73fee66f45d52c67e2aca12fd945dbe0659bf/kotlin-stdlib-jdk7-1.7.10.jar. File exists = true复制代码

可以看出,当运行consumerTask时,执行了 MyTransform,并将jar类型的产物转化成了my-custom-type

TransformActionAGP中的应用

现在AGP中的Transform已经基本上都改成TransformAction了,我们一起来看几个例子

AarTransform

Android ARchive,也就是.aar后缀的资源包,gradle是如何使用它的呢?

如果有同学尝试过就知道,如果是默认使用java-libray的工程,肯定无法依赖并使用aar的,引入时会报Could not resolve ${dependencyNotation},说明在Android Gradle Plugin当中,插件对aar包的依赖进行了处理,只有通过了插件处理,才能正确使用aar内的资源。那就来看看AGP是如何在TransformAction的帮助下做到这点的

Aar转换的实现就是AarTransform,我们一起来看下源码:

  1. // DependencyConfigurator.ktfor (transformTarget in AarTransform.getTransformTargets()) {
  2. registerTransform(
  3. AarTransform::class.java,
  4. AndroidArtifacts.ArtifactType.EXPLODED_AAR,
  5. transformTarget
  6. ) { params ->
  7. params.targetType.setDisallowChanges(transformTarget)
  8. params.sharedLibSupport.setDisallowChanges(sharedLibSupport)
  9. }
  10. }public abstract class AarTransform implements TransformAction<AarTransform.Parameters> { @NonNull
  11. public static ArtifactType[] getTransformTargets() { return new ArtifactType[] {
  12. ArtifactType.SHARED_CLASSES,
  13. ArtifactType.JAVA_RES,
  14. ArtifactType.SHARED_JAVA_RES,
  15. ArtifactType.PROCESSED_JAR,
  16. ArtifactType.MANIFEST,
  17. ArtifactType.ANDROID_RES,
  18. ArtifactType.ASSETS,
  19. ArtifactType.SHARED_ASSETS,
  20. ArtifactType.JNI,
  21. ArtifactType.SHARED_JNI, // ...
  22. };
  23. } @Override
  24. public void transform(@NonNull TransformOutputs transformOutputs) { // 具体实现
  25. }
  26. 复制代码

代码也比较简单,主要做了下面几件事:

  1. DependencyConfigurator中注册Aar转换成各种类型资源的TransformAction

  2. AarTransform中根据类型将aar包中的文件解压到输出到各个目录

JetifyTransform

Jetifier也是在迁移到AndroidX之后的常用功能,它可以将引用依赖内的android.support.*引用都替换为对androidx的引用,从而实现对support包的兼容

下面我们来看一下JetifyTransform的代码

  1. // com.android.build.gradle.internal.DependencyConfiguratorif (projectOptions.get(BooleanOption.ENABLE_JETIFIER)) {
  2. registerTransform(
  3. JetifyTransform::class.java,
  4. AndroidArtifacts.ArtifactType.AAR,
  5. jetifiedAarOutputType
  6. ) { params ->
  7. params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)
  8. }
  9. registerTransform(
  10. JetifyTransform::class.java,
  11. AndroidArtifacts.ArtifactType.JAR,
  12. AndroidArtifacts.ArtifactType.PROCESSED_JAR
  13. ) { params ->
  14. params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)
  15. }
  16. }// com.android.build.gradle.internal.dependency.JetifyTransformoverride fun transform(transformOutputs: TransformOutputs) { val inputFile = inputArtifact.get().asFile val outputFile = transformOutputs.file("jetified-${inputFile.name}")
  17. jetifierProcessor.transform2(
  18. input = setOf(FileMapping(inputFile, outputFile)),
  19. copyUnmodifiedLibsAlso = true,
  20. skipLibsWithAndroidXReferences = true
  21. )
  22. }复制代码
  1. 读取并判断ENABLE_JETIFIER属性,这就是我们在gradle.properties中配置的jetifier开关

  2. aarjar类型的依赖都注册JetifyTransform转换

  3. transform中对support包的依赖进行替换,完成后会将处理过的资源重新压缩,并且会带上jetified的前缀

总结

本文主要讲解了TransformAction是什么,TransformAction自定义,以及TransformActionAGP中的应用,可以看出,目前AGP中的产物转换已经基本上都用TransformAction来实现了

事实上,AGPTransformAction进行了一定的封装,如果你只是想利用ASM实现字节码插桩,那么直接使用AsmClassVisitorFactory就好了。但如果想要阅读AGP的源码,了解AGP构建的过程,还是需要了解一下TransformAction的基本使用与原理的

示例代码

本文所有代码可见:https://github.com/RicardoJiang/Gradle-Tutorials

原文链接:https://juejin.cn/post/7131889789787176974

关注我获取更多知识或者投稿

0dd6da61fe97ed19b065b2812866d9f1.jpeg

b23f898975a5b09f242fefec8885d760.jpeg

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/736466
推荐阅读
  

闽ICP备14008679号