阅读 1417

牛逼的Transform Plus | Transform进阶教程

前言

之前写过一篇文章介绍了下关于Transform增量如何编写。这次我想展开说些别的。

先抛出几个奇奇怪怪的问题。

  1. 如果要写R文件内联的情况下,各位大佬会咋操作,可能会碰到什么问题?
  2. 如果Transform过期不让使用了,那么应该用那种方式抽象会比较好。

最后就是一些我最近玩的一些简单asm相关的东西吧。

AndroidAutoTrack

开始装逼了

如果要在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文件缓存的。

Transform过期了

如果各位有兴趣尝试下升级agp 700 版本的情况下,应该就会发现了Transform已经被标识了废弃了。之前森哥也介绍过这部分,可以参考下面的引用哦。

AGP Transform API 被废弃意味着什么

TransformAction 这个是700版本之后的api,但是恕我太菜,还没学会。

但是boosterbytex 两个牛逼的开源框架,其实都是对Transform有所隔离的,包括我们内部使用的字节码框架也是如此。

这么抽象的好处就是当发生了这种agp的api过期,替换调整的时候,我们就可以避免所有写了Transform的人一起调整了。

只需要底层进行好对应的适配工作,之后让上层开发同学升级下底层库版本就行了。

TODO

这部分我也打算后续一起抽象和开发,配合spi一起,可变成一个动态化框架。

自动化曝光

我之前的文章介绍过关于View的有效曝光监控(上)。我最近对这个也进行了一个小小的改良和尝试,打算还是尝试下配合ASM对其进行调整。

View.OnAttachStateChangeListener

之前我们需要通过自定义很多layout,然后通过修改xml布局内的根节点,之后通过kt的拓展函数去找到具体的layout的方式来开发。

因为涉及到两部分代码,所以我个人看法相对来说使用起来还是比较繁琐的。之后我大佬指点了下我这个api,其实View本身提供了一个addOnAttachStateChangeListener,他的作用和之前介绍的onViewAttachedToWindowonViewDetachedFromWindow是一样的。

这样我们就可以不需要写自定义的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的

文章分类
Android
文章标签