百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Transform 被废弃,TransformAction 了解一下

yuyutoo 2024-10-21 12:09 2 浏览 0 评论

前言

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

国内很多团队都或多或少的用 AGPTransform 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 即可.

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

  1. TransformAction 是什么?
  2. 如何自定义 TransformAction
  3. TransformActionAGP 中的应用

TransformAction是什么?

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

我们在项目中的依赖,可能会有多个变体,例如,一个依赖可能有以下两种变体: classesorg.gradle.usage=java-api , org.gradle.libraryelements=classes )或 JARorg.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 属性,它有两个值: truefalseminified 属性表示依赖项的变体,表示删除了不必要的类文件。

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

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

  1. 如果只有一个转换链,则选择它。
  2. 如果有两个变换链,并且一个是另一个的后缀,则将其选中。
  3. 如果存在最短的变换链,则将其选中。
  4. 在所有其他情况下,选择将失败并报告错误。

同时还有两个特殊情况:

  1. 当已经存在与请求属性匹配的依赖项变体时, Gradle 不会尝试选择产物转换。
  2. artifactType 属性是特殊的,因为它仅存在于解析的产物上,而不存在于依赖项上。因此任何只变换 artifactTypeTransformAction ,只有在使用ArtifactView时才会考虑使用

自定义 TransformAction

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

定义 TransformAction

abstract class Minify : TransformAction<Minify.Parameters> {   // (1)
    interface Parameters : TransformParameters {               // (2)
        @get:Input
        var keepClassesByArtifact: Map<String, Set<String>>

    }

    @get:PathSensitive(PathSensitivity.NAME_ONLY)
    @get:InputArtifact
    abstract val inputArtifact: Provider<FileSystemLocation>

    override
    fun transform(outputs: TransformOutputs) {
        val fileName = inputArtifact.get().asFile.name
        for (entry in parameters.keepClassesByArtifact) {      // (3)
            if (fileName.startsWith(entry.key)) {
                val nameWithoutExtension = fileName.substring(0, fileName.length - 4)
                minify(inputArtifact.get().asFile, entry.value, outputs.file("${nameWithoutExtension}-min.jar"))
                return
            }
        }
        println("Nothing to minify - using ${fileName} unchanged")
        outputs.file(inputArtifact)                            // (4)
    }

    private fun minify(artifact: File, keepClasses: Set<String>, jarFile: File) {
        println("Minifying ${artifact.name}")
        // Implementation ...
    }
}
复制代码

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

TransformAction
transform

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

注册 TransformAction

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

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"
    )
)

dependencies {
    attributesSchema {
        attribute(minified)                      // <1>
    }
    artifactTypes.getByName("jar") {
        attributes.attribute(minified, false)    // <2>
    }
}

configurations.all {
    afterEvaluate {
        if (isCanBeResolved) {
            attributes.attribute(minified, true) // <3>
        }
    }
}

dependencies {                                 // (4)
    implementation("com.google.guava:guava:27.1-jre")
    implementation(project(":producer"))
}

dependencies {
    registerTransform(Minify::class) { // <5>
        from.attribute(minified, false).attribute(artifactType, "jar")
        to.attribute(minified, true).attribute(artifactType, "jar")

        parameters {
            keepClassesByArtifact = keepPatterns
            // Make sure the transform executes each time
            timestamp = System.nanoTime()
        }
    }
}

tasks.register<Copy>("resolveRuntimeClasspath") { 
    from(configurations.runtimeClasspath)
    into(layout.buildDirectory.dir("runtimeClasspath"))
}
复制代码

注册 TransformAction 也分为以下几步:

  1. 添加 minified 属性
  2. 将所有JAR文件的 minified 属性设置为 false
  3. 在所有可解析的配置上设置请求的属性为 minified=true
  4. 添加将要转换的依赖项
  5. 注册 Transformaction ,设置 fromto 的属性,并且传递自定义参数

运行 TransformAction

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

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

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

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

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

变换 ArtifactType的 TransformAction

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

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

class TransformActionPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.run {
            val artifactType = Attribute.of("artifactType", String::class.java)
            dependencies.registerTransform(MyTransform::class.java) { // 1
                it.from.attribute(artifactType, "jar")
                it.to.attribute(artifactType, "my-custom-type")
            }
            val myTaskProvider = tasks.register("myTask", MyTask::class.java) { 
                it.inputCount.set(10)
                it.outputFile.set(File("build/myTask/output/file.jar"))
            }
            val includedConfiguration = configurations.create("includedConfiguration") // 2
            dependencies.add(includedConfiguration.name, "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10")

            val combinedInputs = project.files(includedConfiguration, myTaskProvider.map { it.outputFile })
            val myConfiguration = configurations.create("myConfiguration")
            dependencies.add(myConfiguration.name, project.files(project.provider { combinedInputs }))

            tasks.register("consumerTask", ConsumerTask::class.java) { // 3
                it.artifactCollection = myConfiguration.incoming.artifactView {viewConfiguration ->
                    viewConfiguration.attributes.attribute(artifactType, "my-custom-type")
                }.artifacts
                it.outputFile.set(File("build/consumerTask/output/output.txt"))
            }
        }
    }
}
复制代码

主要分为以下几步:

  1. 声明与注册自定义 Transform ,指定输入与输出的 artifactType
  2. 创建自定义的 configuration ,指定输入的依赖是什么(当然也可以直接用 AGP 已有的 configuration )
  3. 在使用时,通过自定义 configurationartifactView ,获取对应的产物
  4. ConsumerTask 中消费自定义 TransformAction 的输出产物

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

> Task :app:consumerTask
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 = true
Processing ~/AndroidProject/2022/argust/GradleTutorials/app/build/myTask/output/file.jar. File exists = true
Processing ~/.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 = true
Processing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar. File exists = true
Processing ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/d2abf9e77736acc4450dc4a3f707fa2c10f5099d/kotlin-stdlib-1.7.10.jar. File exists = true
Processing ~/.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

TransformAction在 AGP中的应用

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

AarTransform

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

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

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

// DependencyConfigurator.kt
for (transformTarget in AarTransform.getTransformTargets()) {
    registerTransform(
        AarTransform::class.java,
        AndroidArtifacts.ArtifactType.EXPLODED_AAR,
        transformTarget
    ) { params ->
        params.targetType.setDisallowChanges(transformTarget)
        params.sharedLibSupport.setDisallowChanges(sharedLibSupport)
    }
}

public abstract class AarTransform implements TransformAction<AarTransform.Parameters> {

    @NonNull
    public static ArtifactType[] getTransformTargets() {
        return new ArtifactType[] {
            ArtifactType.SHARED_CLASSES,
            ArtifactType.JAVA_RES,
            ArtifactType.SHARED_JAVA_RES,
            ArtifactType.PROCESSED_JAR,
            ArtifactType.MANIFEST,
            ArtifactType.ANDROID_RES,
            ArtifactType.ASSETS,
            ArtifactType.SHARED_ASSETS,
            ArtifactType.JNI,
            ArtifactType.SHARED_JNI,
            // ...
        };
    }

    @Override
    public void transform(@NonNull TransformOutputs transformOutputs) {
        // 具体实现
    }        
复制代码

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

  1. DependencyConfigurator 中注册 Aar 转换成各种类型资源的 TransformAction
  2. AarTransform 中根据类型将 aar 包中的文件解压到输出到各个目录

JetifyTransform

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

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

// com.android.build.gradle.internal.DependencyConfigurator

if (projectOptions.get(BooleanOption.ENABLE_JETIFIER)) {
    registerTransform(
        JetifyTransform::class.java,
        AndroidArtifacts.ArtifactType.AAR,
        jetifiedAarOutputType
    ) { params ->
        params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)
    }
    registerTransform(
        JetifyTransform::class.java,
        AndroidArtifacts.ArtifactType.JAR,
        AndroidArtifacts.ArtifactType.PROCESSED_JAR
    ) { params ->
        params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)
    }
}

// com.android.build.gradle.internal.dependency.JetifyTransform
override fun transform(transformOutputs: TransformOutputs) {
    val inputFile = inputArtifact.get().asFile

    val outputFile = transformOutputs.file("jetified-${inputFile.name}")
    jetifierProcessor.transform2(
        input = setOf(FileMapping(inputFile, outputFile)),
        copyUnmodifiedLibsAlso = true,
        skipLibsWithAndroidXReferences = true
    )
}
复制代码
  1. 读取并判断 ENABLE_JETIFIER 属性,这就是我们在 gradle.properties 中配置的 jetifier开关
  2. aarjar 类型的依赖都注册 JetifyTransform 转换
  3. transform 中对 support 包的依赖进行替换,完成后会将处理过的资源重新压缩,并且会带上 jetified 的前缀

总结

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

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

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: