Transform 被废弃,ASM 如何适配?
前言
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<InstrumentationParameters.None> {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<out String>?): MethodVisitor {val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)val newMethodVisitor =object : AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, descriptor) {@Overrideoverride fun onMethodEnter() {// 方法开始if (isNeedVisiMethod(name)) {mv.visitLdcInsn(name);mv.visitMethodInsn(INVOKESTATIC, "com/zj/android_asm/TimeCache", "putStartTime","(Ljava/lang/String;)V", false);}super.onMethodEnter();}@Overrideoverride 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)}}
}
- 基于
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%的性能提升,加快编译速度。
最后,保持好的开发习惯,砍掉不必要的功能才是保证包体积持续优化的超级大招,这里再给大家分享一份一线大厂开发都有的 《Android 核心笔记汇总》,如有需要参考的可以私信回复 666 即可取货!!!
Transform 被废弃,ASM 如何适配?相关推荐
- 【我的ASM学习进阶之旅】 介绍一个基于gradle transform api和ASM的字节码插件平台ByteX
原文链接: https://github.com/bytedance/ByteX/blob/master/README_zh.md 文章目录 ByteX(Infinite Possibilities) ...
- 解决web前端高分辨率电视(4K)适配问题
因为自己没适配过电视,所以页面是按照3840*2610开发的,但是用电视打开后发现分辨率变了,变成了960*540.一下给我整不会了. 想了想使用了transform对页面进行了适配 此方法可以用于高 ...
- 【金三银四】2022 Android面经实录
1. 前言 春水初盛,垂钓者络绎不绝,鱼儿按捺不住,拍打着尾鳍纷纷跃出水面,沽个好价. 本篇真实的记录了我从 准备->复习->面试 的全过程,分享一些我的真实经验,希望能帮到大家. 2. ...
- 【vue】vue-znly
老规矩,放下博主的项目地址:https://github.com/wohaiwo/vue-znly 我一直在想给那些开源者取什么名字比较好,怎样才对得起他们开源项目的精神,后来想想,还是叫博主吧.有的 ...
- spring boot 2.x静态资源会被HandlerInterceptor拦截的原因和解决方法
转载:https://my.oschina.net/dengfuwei/blog/1795346 spring boot 2.x静态资源会被HandlerInterceptor拦截的原因和解决方法 ...
- 货拉拉 Android 动态资源管理系统原理与实践(下)
点击上方蓝字关注我,知识会给你力量 so资源动态化方案 so资源打包问题 在打包so资源的过程中,我们遇到了如下问题. 如何移除apk中的so文件,并将他们收集起来? 如何将多个so文件压缩打包,并生 ...
- css鼠标划过时的一些小特效
css部分----------- .div1{width: 100px; height: 100px; border: 1px solid; position: relative; backgroun ...
- 技术漫谈之——Jectpack Compose
最近Jetpack Compose发布了Beta版本,抽时间了解了一下Compose带来的改变和其中的一些原理.本文不会讲解具体API,只是比较随意的分享自己的一些疑问以及在探寻答案过程中的一些收获. ...
- 在线调试工具 Arthas
Arthas(阿尔萨斯) 是 Alibaba开源的一款 Java在线诊断工具,能够分析,诊断,定位Java应用问题,例如:JVM信息,线程信息,搜索类中的方法,跟踪代码执行,观测方法的入参和返回参数等 ...
最新文章
- 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?
- 中国开封菊花花会照片1
- win8: 清除iframe的缓存
- 小尼机器人_小尼被机器人嫌弃“唱歌难听,长相一般”?
- VC 忽略警告的方法
- 8 stark组件 展示数据
- 加快android编译速度
- 批处理写的关机小程序--bat
- UVa1586 - Molar mass
- 安卓基础干货(四):安卓网络编程的学习
- 2015上海网络赛 HDU 5475 An easy problem 线段树
- 渗透场景篇--当XSS遇上CSRF
- bootstrap项目中无间距栅格(grid) no-gutter
- MyBatis源码阅读(十) --- 一级缓存、二级缓存工作原理
- 四年级计算机上册课程标准,新课程标准人教版四年级上册数学全册教案设计
- C++ 虚函数实现:虚函数表 虚表指针
- JAVA:实现Lucas Series卢卡斯系列算法(附完整源码)
- UOJ #34 多项式乘法
- 旧电脑装什么系统最快_【老旧电脑装什么系统】老电脑装什么系统流畅_老旧电脑收藏...
- mapstruct使用指南(2)