前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >牛逼的Transform Plus | Transform进阶教程

牛逼的Transform Plus | Transform进阶教程

作者头像
逮虾户
发布2022-03-06 09:54:09
发布2022-03-06 09:54:09
62600
代码可运行
举报
文章被收录于专栏:逮虾户逮虾户
运行总次数:0
代码可运行

前言

之前写过一篇文章介绍了下关于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 速度会更快。

代码语言:javascript
代码运行次数:0
复制
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插入一个调用就行了。

代码语言:javascript
代码运行次数:0
复制
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的初始化函数中插入我们的曝光监控的类就行了。

这部分逻辑相对来说也比较简单,我简单说下就好了。

代码语言:javascript
代码运行次数:0
复制
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))
                    }
                }
            }
        }
    }

}
代码语言:javascript
代码运行次数:0
复制
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的

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 开始装逼了
    • 二次扫描
    • 增量缓存
    • Transform过期了
      • TODO
    • 自动化曝光
      • View.OnAttachStateChangeListener
      • 自动插入曝光监听
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档