Transform API 是 AGP1.5 就引入的特性,主要用于在 Android 构建过程中,在 Class转Dex的过程中修改 Class 字节码。利用 Transform API,我们可以拿到所有参与构建的 Class 文件,然后可以借助ASM 等字节码编辑工具进行修改,插入自定义逻辑。
国内很多团队都或多或少的用 AGP 的 Transform API 来搞点儿黑科技,比如无痕埋点,耗时统计,方法替换等。但是在AGP7.0中Transform已经被标记为废弃了,并且将在AGP8.0中移除。而AGP8.0应该会在今年内发布,可以说是已经近在眼前了。所以现在应该是时候了解一下,在Transform被废弃之后,该怎么适配了。
Transform Action介绍
Transform API是由AGP提供的,而Transform Action则是由Gradle提供。不光是 AGP 需要 Transform,Java 也需要,所以由 Gradle 来提供统一的 Transform API 也合情合理。 这应该也是Transform API被废弃的原因,既然Gradle已经统一提供了API,AGP也就没必要自定义一套了。
关于 TransformAction 如何使用,Gradle 官方已经提供了很详细的文档–Transforming dependency artifacts on resolution,具体使用可以直接参考文档
AsmClassVisitorFactory介绍
直接使用Transform Action的话还是有些麻烦,跟Transform API一样,需要手动处理增量编译的逻辑。AGP很贴心的为我们又做了一层封装,提供了AsmClassVisitorFactory来方便我们使用Transform Action进行ASM操作。 根据官方的说法,AsmClassVisitoFactory会带来约18%的性能提升,同时可以减少约5倍代码
接下来我们利用AGP 的AsmClassVisitorFactory API,来实现方法执行耗时的插桩。
AsmClassVisitorFactory
abstract class TimeCostTransform: AsmClassVisitorFactory {
override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
return TimeCostClassVisitor(nextClassVisitor)
}
override fun isInstrumentable(classData: ClassData): Boolean {
return true
}
}
AsmClassVisitorFactory即创建ClassVisitor对象的工厂。此接口的实现必须是一个抽象类,createClassVisitor返回我们自定义的ClassVisitor,在自定义Visitor处理完成后,需要传内容传递给下一个Visitor,因此我们将其放在构造函数中传入isInstrumentable用于控制我们的自定义Visitor是否需要处理这个类,通过这个方法可以过滤我们不需要的类,加快编译速度
ClassVisitor
class TimeCostClassVisitor(nextVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM5, nextVisitor) {
override fun visitMethod(
access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array?
): 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 != "" && name != "printlnTime" && name != ""
}
}
这里就跟普通的ASM操作没什么不同了,主要是通过ASM字节码插桩,在方法的前后插入如下代码,通过计算两者的时间差来得出方法的耗时
fun timeMethod(){
TimeCache.putStartTime("timeMethod") //方法开始插入的代码
Thread.sleep(1000)
TimeCache.putEndTime("timeMethod") //方法结束插入的代码
}
注册Transform
老版本的Transform是注册在AppExtension中的,新版本则是注册在AndroidComponentsExtension中
class TimeCostPlugin : Plugin {
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
)
}
}
}
- 基于
variant可实现不同的变种不同的处理逻辑 transformClassesWith通过InstrumentationScope控制是否需要扫描依赖库代码setAsmFramesComputationMode可设置不同的栈帧计算模式,具体可查看源码
AsmClassVisitorFactory的优势
通过以上步骤,一个简单的通过插桩计算方法执行耗时的功能就完成了,这比起老版的Transform API其实简化了不少,老版本处理增量更新就需要处理一大堆的逻辑。 可以看到我们这里并没有手动处理增量逻辑,这是因为调用AsmClassVisitorFactory的TransformClassesWithAsmTask继承自NewIncrementalTask,已经处理了增量逻辑,不需要我们再手动处理了
同时老版本的Transform每个Transfrom各自独立,如果每个Transform编译构建耗时+10s,各个Transform叠在一起,编译耗时就会呈线性增长 而新版本可以看出我们也没有手动进行IO操作,这是因为AsmInstrumentationManager中已经做了统一处理,只需要进行一次IO操作,然后交给ClassVisitor链表处理,完成后统一交给ClassWriter写入 通过这种方式,可以有效地减少IO操作,这也是新版本API性能提升的原因
总得来说,由于Transform API在AGP7.0已标记为废弃,并且将在AGP8.0中移除,是时候了解一下如何迁移Transform API了
而AsmClassVisitorFactory相比Transform API,使用起来更加简单,不需要手动处理增量逻辑,可以专注于字节码插桩操作。同时AsmClassVisitorFactory通过减少IO的方式,可以得到约20%的性能提升,加快编译速度。
作者:程序员江同学 链接:https://juejin.cn/post/7105925343680135198
