之前写过一篇文章介绍了下关于Transform
增量如何编写。这次我想展开说些别的。
先抛出几个奇奇怪怪的问题。
R文件内联
的情况下,各位大佬会咋操作,可能会碰到什么问题?最后就是一些我最近玩的一些简单asm相关的东西吧。
如果要在R文件内联
的情况下,可能会碰到什么问题?
在安卓agp 4.1.0 版本上 R8已经做了这部分的内联操作了,完全不需要我们再去写这么个转换
因为在Transform
操作过程中,我们是通过文件路径的形式去访问一个个class文件的,这种情况下就无法确保访问的是否是有序和有语法的。
因为我们首先要取到的是R文件,然后把R文件的内容收集起来,之后再去所有有使用到R文件的类中,所以如果使用正常的Transform
流程的话这个可能就没办法操作了.
然后通过文件或者类信息进行增量编译的缓存数据,因为在增量编译得时候我们需要对上次的R文件进行一次记录,否则增量情况下就会出现缺失的问题。这部分尤其容易发生在路由表增量的过程中。
如果我们把文件扫描的操作拆分成两个部分,第一次只进行数据收集,第二次则是在第一次收集完数据的基础上在去做修改,是不是就可以了呢?
R文件内联
这种复杂的asm操作的时候,我们同理是不是就可以非常完美的解决这部分问题了呢。
第一次扫描我们只进行asm文件访问,而不进行asm修改。我们在这个过程中只收集我们所需要的数据信息,当然这次操作我们不会进行任何的asm替换操作和文件写入操作,只会将文件转化成asm语法相关的。当然这里是用tree api
还是core api
就随便了。
asm 的依赖库有两种 tree api 和core api ,差别就是tree api 基于node 语法树,而core api 速度会更快。
api 'org.ow2.asm:asm:9.2'
api 'org.ow2.asm:asm-tree:9.2'
第二次asm操作的时候我们会进行文件copy操作以及类替换等等,第二次的时候我们会在第一次收集到的类数据的,之后将原来要做的类替换或者方法替换等还原出来。
各位可以仔细品一品,是不是基本上任何复杂的都可以基于这套逻辑去操作。但是我还是不是特别建议大家去写这种,毕竟也还是比较容易出黑锅的。
这部分我感觉路由表的增量就很容易解释这个情况。
当我们要进行第二次增量编译的时候,由于增量编译的特性,只有变更文件需要进行修改,但是这个时候之前的路由表是需要进行还原。
这部分可以参考下我自己写的路由的Transform
或者DRouter,我之前分析过,因为我的路由表的注册类是class,而不是jar。所以我是通过asm读取当前类进行上一次的路由表回溯的。之后我就会将本次的增删改操作作用在这个注册类上就行了。
而DRouter则是生成了一个json文件,通过json文件去记录上一次的路由表,然后在增量编译的时候对这个路由表进行修改,然后等这次编译完成之后将json文件进行覆盖操作。
而我们哔哩哔哩自研的BRouter的增量缓存实现则更加诡异,我们是基于javac的task之后的,也同样是基于json文件缓存的。
如果各位有兴趣尝试下升级agp 700 版本的情况下,应该就会发现了Transform
已经被标识了废弃了。之前森哥也介绍过这部分,可以参考下面的引用哦。
TransformAction
这个是700版本之后的api,但是恕我太菜,还没学会。
但是booster
和bytex
两个牛逼的开源框架,其实都是对Transform
有所隔离的,包括我们内部使用的字节码框架也是如此。
这么抽象的好处就是当发生了这种agp的api过期,替换调整的时候,我们就可以避免所有写了Transform
的人一起调整了。
只需要底层进行好对应的适配工作,之后让上层开发同学升级下底层库版本就行了。
这部分我也打算后续一起抽象和开发,配合spi一起,可变成一个动态化框架。
我之前的文章介绍过关于View的有效曝光监控(上)。我最近对这个也进行了一个小小的改良和尝试,打算还是尝试下配合ASM对其进行调整。
之前我们需要通过自定义很多layout,然后通过修改xml布局内的根节点,之后通过kt的拓展函数去找到具体的layout的方式来开发。
因为涉及到两部分代码,所以我个人看法相对来说使用起来还是比较繁琐的。之后我大佬指点了下我这个api,其实View本身提供了一个addOnAttachStateChangeListener
,他的作用和之前介绍的onViewAttachedToWindow
和onViewDetachedFromWindow
是一样的。
这样我们就可以不需要写自定义的View替换了,只需要给itemView插入一个调用就行了。
viewTreeObserver.addOnWindowFocusChangeListener(this)
view.addOnAttachStateChangeListener(this)
}
override fun onViewAttachedToWindow(v: View?) {
exposeChecker.updateStartTime()
}
override fun onViewDetachedFromWindow(v: View?) {
onExpose()
// exposeChecker.updateStartTime()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
exposeChecker.updateStartTime()
} else {
onExpose()
}
}
private fun onExpose() {
if (exposeChecker.isViewExpose(view)) {
mListener?.onExpose(exposeChecker.exposeTime)
}
}
}
简单的说哦,胆码就上面这么一丢丢了,有兴趣还是可以去AutoTrack
那个工程去看下。
我们写完了上面的代码之后,就基本解决了xml方面的问题了,那么剩下来的就是我们需要在RecyclerView.ViewHolder的初始化函数中插入我们的曝光监控的类就行了。
这部分逻辑相对来说也比较简单,我简单说下就好了。
class RecyclerViewHolderImp(classNode: ClassNode) {
init {
if (isRecyclerViewHolder(classNode.superName)) {
classNode.methods.firstOrNull {
it.name == "<init>"
}?.apply {
instructions.forEach {
if (it.methodEnd()) {
instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 1))
instructions.insertBefore(it, TypeInsnNode(Opcodes.NEW,
"com/wallstreetcn/sample/viewexpose/AutoExposeImp"))
instructions.insertBefore(it, InsnNode(Opcodes.DUP))
instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
instructions.insertBefore(it, MethodInsnNode(Opcodes.INVOKESPECIAL, "com/wallstreetcn/sample/viewexpose/AutoExposeImp",
"<init>", "(Landroidx/recyclerview/widget/RecyclerView\$ViewHolder;)V", false))
instructions.insertBefore(it, MethodInsnNode(Opcodes.INVOKESTATIC, "com/wallstreetcn/sample/viewexpose/ItemViewExtensionKt",
"addExposeListener", "(Landroid/view/View;Lcom/wallstreetcn/sample/viewexpose/OnExposeListener;)V", false))
}
}
}
}
}
}
class AutoExposeImp(private val viewHolder: RecyclerView.ViewHolder) : OnExposeListener {
override fun onExpose(exposeTime: Float) {
}
}
fun View.addExposeListener(listener: OnExposeListener?) {
val exposeDelegate = ExposeViewDelegate(this, listener)
}
判断下当前的类是不是RecyclerView.ViewHolder
的实现类,如果是就在<init>
方法的最后插入一个AutoExposeImp
的曝光实现类的静态代码。
这部分具体的源代码都在AndroidAutoTrack这个内部有sample,有兴趣的老哥可以参考学习下呢。
其实asm的难度并不算特别的高吧,只要你多尝试多写写多调试,并不需要你有多聪明,就可以掌握这门技巧的,而且有反向工具帮助的情况下哦,其实更多的操作就是类似复制黏贴而已。
另外还有就是有机会多了解多看看一些很强的开源框架,他们的实现是什么。可以帮助我们去理解和抽象代码。
booster 森哥的Gradle开源框架 写的真的非常的棒 森哥牛逼 宇宙第一
ByteX 字节的字节码开源框架 也是一个非常牛逼的框架了
lancet 我们大佬早期作品 也是牛逼到离谱的一个字节码框架
BRouter 我们哔哩哔哩的路由框架 真的牛逼
DRouter 滴滴的路由 其实有点点意思 为了节省编译时间 所以全部都是基于Transform的