1
前言
Transform API 是 AGP1.5 就引入的特性,主要用于在 Android 构建过程中,在 Class转Dex的过程中修改 Class 字节码。利用 Transform API,我们可以拿到所有参与构建的 Class 文件,然后可以借助ASM 等字节码编辑工具进行修改,插入自定义逻辑。
国内很多团队都或多或少的用 AGP 的 Transform API 来搞点儿黑科技,比如无痕埋点,耗时统计,方法替换等。但是在AGP7.0中Transform已经被标记为废弃了,并且将在AGP8.0中移除。而AGP8.0应该会在今年内发布,可以说是已经近在眼前了。所以现在应该是时候了解一下,在Transform被废弃之后,该怎么适配了。
2
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,具体使用可以直接参考文档。
https://docs.gradle.org/current/userguide/artifact_transforms.html
3
AsmClassVisitorFactory介绍
直接使用Transform Action的话还是有些麻烦,跟Transform API一样,需要手动处理增量编译的逻辑。AGP很贴心的为我们又做了一层封装,提供了AsmClassVisitorFactory来方便我们使用Transform Action进行ASM操作。根据官方的说法,AsmClassVisitoFactory会带来约18%的性能提升,同时可以减少约5倍代码。
4
代码实战
接下来我们利用AGP 的AsmClassVisitorFactory API,来实现方法执行耗时的插桩。
实现AsmClassVisitorFactory
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返回我们自定义的ClassVisitor,在自定义Visitor处理完成后,需要传内容传递给下一个Visitor,因此我们将其放在构造函数中传入。
3. isInstrumentable用于控制我们的自定义Visitor是否需要处理这个类,通过这个方法可以过滤我们不需要的类,加快编译速度。
自定义ClassVisitor
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操作没什么不同了,主要是通过ASM字节码插桩,在方法的前后插入如下代码,通过计算两者的时间差来得出方法的耗时。
fun timeMethod(){
TimeCache.putStartTime("timeMethod") //方法开始插入的代码
Thread.sleep(1000)
TimeCache.putEndTime("timeMethod") //方法结束插入的代码
}
注册Transform
老版本的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可实现不同的变种不同的处理逻辑。
2. transformClassesWith通过InstrumentationScope控制是否需要扫描依赖库代码。
3. setAsmFramesComputationMode可设置不同的栈帧计算模式,具体可查看源码。
AsmClassVisitorFactory的优势
通过以上步骤,一个简单的通过插桩计算方法执行耗时的功能就完成了,这比起老版的Transform API其实简化了不少,老版本处理增量更新就需要处理一大堆的逻辑。
可以看到我们这里并没有手动处理增量逻辑,这是因为调用AsmClassVisitorFactory的TransformClassesWithAsmTask继承自NewIncrementalTask,已经处理了增量逻辑,不需要我们再手动处理了。
同时老版本的Transform每个Transfrom各自独立,如果每个Transform编译构建耗时+10s,各个Transform叠在一起,编译耗时就会呈线性增长。
而新版本可以看出我们也没有手动进行IO操作,这是因为AsmInstrumentationManager中已经做了统一处理,只需要进行一次IO操作,然后交给ClassVisitor链表处理,完成后统一交给ClassWriter写入。
通过这种方式,可以有效地减少IO操作,这也是新版本API性能提升的原因。
5
总结
总得来说,由于Transform API在AGP7.0已标记为废弃,并且将在AGP8.0中移除,是时候了解一下如何迁移Transform API了。
而AsmClassVisitorFactory相比Transform API,使用起来更加简单,不需要手动处理增量逻辑,可以专注于字节码插桩操作。同时AsmClassVisitorFactory通过减少IO的方式,可以得到约20%的性能提升,加快编译速度。
示例代码
本文所有源码可见:
https://github.com/shenzhen2017/Android-ASM
参考资料
其实 Gradle Transform 就是个纸老虎 —— Gradle 系列(4)
https://juejin.cn/post/7098752199575994405
现在准备好告别Transform了吗?| 拥抱AGP7.0
https://juejin.cn/post/7016147287889936397