Transform 被废弃,ASM 如何适配?
theme: smartblue highlight: a11y-dark
前言
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
```kotlin
abstract class TimeCostTransform: AsmClassVisitorFactory
override fun isInstrumentable(classData: ClassData): Boolean {
return true
}
} ```
AsmClassVisitorFactory
即创建ClassVisitor
对象的工厂。此接口的实现必须是一个抽象类,createClassVisitor
返回我们自定义的ClassVisitor
,在自定义Visitor
处理完成后,需要传内容传递给下一个Visitor
,因此我们将其放在构造函数中传入isInstrumentable
用于控制我们的自定义Visitor
是否需要处理这个类,通过这个方法可以过滤我们不需要的类,加快编译速度
自定义ClassVisitor
```kotlin
class TimeCostClassVisitor(nextVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM5, nextVisitor) {
override fun visitMethod(
access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array
@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
字节码插桩,在方法的前后插入如下代码,通过计算两者的时间差来得出方法的耗时
kotlin
fun timeMethod(){
TimeCache.putStartTime("timeMethod") //方法开始插入的代码
Thread.sleep(1000)
TimeCache.putEndTime("timeMethod") //方法结束插入的代码
}
注册Transform
老版本的Transform
是注册在AppExtension
中的,新版本则是注册在AndroidComponentsExtension
中
kotlin
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
)
}
}
}
- 基于
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%的性能提升,加快编译速度。
示例代码
本文所有源码可见:http://github.com/shenzhen2017/Android-ASM
参考资料
其实 Gradle Transform 就是个纸老虎 —— Gradle 系列(4)
现在准备好告别Transform了吗? | 拥抱AGP7.0
- kotlin-android-extensions 插件到底是怎么实现的?
- 江同学的 2022 年终总结,请查收~
- kotlin-android-extensions 插件将被正式移除,如何无缝迁移?
- 学习一下 nowinandroid 的构建脚本
- Kotlin 默认可见性为 public,是不是一个好的设计?
- 2022年编译加速的8个实用技巧
- 落地 Kotlin 代码规范,DeteKt 了解一下~
- Gradle 进阶(二):如何优化 Task 的性能?
- 开发一个支持跨平台的 Kotlin 编译器插件
- 开发你的第一个 Kotlin 编译器插件
- Kotlin 增量编译是怎么实现的?
- Gradle 都做了哪些缓存?
- K2 编译器是什么?世界第二高峰又是哪座?
- Android 性能优化之 R 文件优化详解
- Kotlin 快速编译背后的黑科技,了解一下~
- 别了 KAPT , 使用 KSP 快速实现 ButterKnife
- Android Apk 编译打包流程,了解一下~
- 如何优雅地扩展 AGP 插件
- ASM 插桩采集方法入参,出参及耗时信息
- Transform 被废弃,ASM 如何适配?