前言

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

github永久回家地址内很多团队都或多或少的用 AGPTransform API 来搞点儿字节码黑科技,比如无痕埋点,耗时统计,方法替换等。但是在AGP7.0Transform已经被标记为废弃了,并且将在AGP8.0中移除。而AGP8.0应该会在今年内发字节码布,可以说是已经近在眼前了。所以现在应该是时候了解一下,在Transform被废弃之后,该怎么适配了。

Transform Action介绍

Transform API是由AGP提供github汤姆的,而Transform Action则是由Gjavascriptradgitlable提供。不光是 AGP 需要 TransformJava 也需要,所以由 Gradle 来提供统一的 Transform API 也合情合字节码是什么意思理。
这应该APP也是Transform API被废弃的原因,既然Gradle已经APP统一提供了APIAGP也就没必要自定义一套了。

关于 TransformAction 如何使用,Gradle 官方已github官网登陆入口经提供了很详细的文档–Transforming dependency artifacts on resolution,具体使用可以直接参考文档

AsmClassVisitorFactory介绍

直接使用Transform Action的话还是有些麻烦,跟Transform API一样,需要手动处理增量编译的逻辑。AGP很贴心的为我们又做了一层封装,提供了AsmClassVisitorFactory来方便我们使用Transformgithub中文社区 Action进行ASM操作。 根据官方的说法,AsmClassVisitoFactory会带来约18%的性能提升github官网登陆入口,同时可以减少约5倍代码

Transform 被废弃,ASM 如何适配?

代码实战

接下来我们利用AGPAsmClassVisitorFactory API,来实现方法执行耗时的插桩。

实现Asgithub下载mClassVisitorFactory

abstract class TimeCostTransform: AsmClassVisitorFactory<InstrumentationParameters.None> {
    override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
        return TimeCostClassVisitor(nextClassVisitor)
    }
    override fun isInstrumentable(classData: ClassData): Boolean {
        return true
    }
}
  1. AsmClassVisitorFactory即创建ClassVisitor字节码文件扩展名象的工厂。此接口的实现必须是一个抽象类,
  2. createClassVisitor返回我们自定义的ClassVgithub官网登陆入口isitor,在自定义Visitor处理完成后,需要传内容传递给javaee下一个Visitapproveor,因此我们将其放在构造函数中传入
  3. isInstrumentablejavaee于控制我们的自定义Visitor是否需要处理这个类,通过这个方法可以过滤我们不字节码是什么意思需要的类,加快编译速度

自定义ClassVisitjava环境变量配置or

class TimeCostClassVisitor(nextVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM5, nextVisitor) {
    override fun visitMethod(
        access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array<out String>?
    ): MethodVisitor {
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
        val newMethodVisitor =
            object : AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, descriptor) {
                @Override
                override fun onMethodEnter() {
                    // 方法开始
                    if (isNeedVisiMethod(name)) {
                        mv.visitLdcInsn(name);
                        mv.visitMethodInsn(
                            INVOKESTATIC, "com/zj/android_asm/TimeCache", "putStartTime","(Ljava/lang/String;)V", false
                        );
                    }
                    super.onMethodEnter();
                }
                @Override
                override fun onMethodExit(opcode: Int) {
                    // 方法结束
                    if (isNeedVisiMethod(name)) {
                        mv.visitLdcInsn(name);
                        mv.visitMethodInsn(
                            INVOKESTATIC, "com/zj/android_asm/TimeCache", "putEndTime","(Ljava/lang/String;)V", false
                        );
                    }
                    super.onMethodExit(opcode);
                }
            }
        return newMethodVisitor
    }
    private fun isNeedVisiMethod(name: String?):Boolean {
        return name != "putStartTime" && name != "putEndTime" && name != "<clinit>" && name != "printlnTime" && name != "<init>"
    }
}

这里就跟普通的ASM操作没什么不同了,主要是apple通过ASM字节码插桩,在方法的前后插入如下代码,通过计算两者的时间差来得出方法的耗时

    fun timeMethod(){
        TimeCache.putStartTime("timeMethod") //方法开始插入的代码
        Thread.sleep(1000)
        TimeCache.putEndTime("timeMethod") //方法结束插入的代码
    }

注册Tranjava模拟器sform

老版本的Transform是注册在AppExtension中的,新版本则是注册在AndroidComponentsExtension

class TimeCostPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.onVariants { variant ->
            variant.instrumentation.transformClassesWith(TimeCostTransform::class.java,
                    InstrumentationScope.PROJECT) {}
            variant.instrumentation.setAsmFramesComputationMode(
                    FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
            )
        }
    }
}
  1. 基于variant可实现不同appetite的变种不同的处理逻辑
  2. transfogithub汤姆rmClassesWitappearh通过InstrumentationScope控制是否需要扫描appstore依赖库代码
  3. setAsmFramesComputationMod字节码是什么意思e可设置不同的栈帧计算模式,具体可查看源码

AsmClassVisitorFactory的优势

通过以上步骤,一个简单的通过插桩计算方法执行耗时的功能就完成了,这比起老版的Transform API其实简化了不少,老版本处理增appointment量更新就需要处理一大堆的逻辑。
可以看到我们这里并没有手动处理增量逻辑,这是因为调用AsmClassVisitorFactoryTransformClassesWithAsmTask继承自NewIncrementaljavaeeTask,已经处理了增量逻辑,不需要我们再手动处理了

同时application老版本的Transform每个Transfrom各自独立,如果每个Transform编译构建耗时+10s,各个Transform叠在一起,编译耗时就会呈线appear性增长
而新版本可以看出我们也没有手动进行IO操作,这是因为AsmInstrumentationManagegithub中文官网网页r中已经做了统一处理,只需要进行一次IO操作,然后交给ClassVisitor链表处理,完成后统一交appearClaGitHubssWriter写入
通过这种方式,可以有效地减少IO操作,这也是新版本API性能提升的原因

总结

总得来说,由于Transform APIAGP7.0已标记为废弃,并且将在AGP8.0中移除,是时候了解一下如何迁java语言Transform API

AsmClasappetitesVisitorFactory相比Transform API,使用起来更加简单,不需要javascript手动处理增量逻辑,可以专注于字节码插桩操作。同时AsmClassVisitorFactory通过减少IO的方式,可以得approach到约20%的性能提升,加快编译速度。

示例代码

本文所有源码可见:github.com/shenzhen201…

参考资料

其实 Gradle Transfogithub永久回家地址rm 就是个纸老虎 —— Gr字节码文件扩展名adle 系列(4)
现在准备好告appleapproachTransform了approve吗? | 拥抱AGP7.0