##第4讲 编译插桩操纵字节码
拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm
这一讲的内容对我来说挺新鲜的,编译插桩只听过这个词,并一直认为是一项高不可及的黑科技,看完这节课,感觉还是没那么可怕的。这节课举了一个例子手把手地说明了怎么实现编译插桩。这里记录一下思路,具体代码看课程。
0、需求
先说一下需求,不然说了半天都不知道用这个插桩来干什么。
需求:
记录每一个页面的打开和关闭事件,并通过各种 DataTracking 的框架上传到服务器,用来日后做数据分析。
实际演示的需求是在每个activity的onCreate方法中打印一行日志
面对这样的需求,一般人都会想到,这其实就是在每一个 Activity 的 onCreate 和 onDestroy 方法中,分别添加页面打开和页面关闭的逻辑。常见的做法有以下两种:
- 修改项目中现有的每一个 Activity,这样显然不够高大上,并且如果项目以后需要添加新的页面,这套逻辑需要重新拷贝一遍,非常容易遗漏。
- 将项目中所有的 Activity 继承自 BaseActivity,将页面打开和关闭的逻辑添加在 BaseActivity中,这种方案看起来比第 1 种方案高级得多,并且后续项目中有新的 Activity,直接继承 BaseActivity 即可。但是这种方案对第三方依赖库中的界面则无能为力,因为我们没有第三方依赖库的源码。
就是在这种环境下,一种更加优雅更加完整的方案应运而生:编译插桩。
一、编译插桩是什么
编译插桩就是在代码编译期间修改已有的代码或者生成新代码。
先回顾一下 Android 项目中 .java 文件的编译过程:
从上图可以看出,我们可以在 1、2 两处对代码进行改造。
- 在 .java 文件编译成 .class 文件时,APT、AndroidAnnotation 等就是在此处触发代码生成。
- 在 .class 文件进一步优化成 .dex 文件时,也就是直接操作字节码文件,也是本课时主要介绍的内容。这种方式功能更加强大,应用场景也更多。但是门槛比较高,需要对字节码有一定的理解
课程主要介绍第 2 种实现方式,过程如下示意图:
二、插桩工具介绍
目前市面上主要流行两种实现编译插桩的方式:
1、AspectJ(我表示没听过)
AspectJ 是老牌 AOP(Aspect-Oriented Programming)框架,如果你做过 J2EE 开发可能对这个框架更加熟悉,经常会拿这个框架跟 Spring AOP 进行比较。其主要优势是成熟稳定,使用者也不需要对字节码文件有深入的理解。
2、ASM(我仅见过这个词)
目前另一种编译插桩的方式 ASM 越来越受到广大工程师的喜爱。通过 ASM 可以修改现有的字节码文件,也可以动态生成字节码文件,并且它是一款完全以字节码层面来操纵字节码并分析字节码的框架。
ASM 是一套开源框架,其中几个常用的 API 如下:
ClassReader:负责解析 .class 文件中的字节码,并将所有字节码传递给 ClassWriter。
ClassVisitor:负责访问 .class 文件中各个元素,还记得上一课时我们介绍的 .class 文件结构吗?ClassVisitor 就是用来解析这些文件结构的,当解析到某些特定结构时(比如类变量、方法),它会自动调用内部相应的 FieldVisitor 或者 MethodVisitor 的方法,进一步解析或者修改 .class 文件内容。
ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,负责将修改后的字节码输出为 byte 数组。
二、实际思路
1、 将所有的.class文件找出来
怎么找?
通过自定义gradle插件来实现(具体是通过自定义插件内的 Transform来实现)
什么是 Transform ?
Transform 可以被看作是 Gradle 在编译项目时的一个 task,在 .class 文件转换成 .dex 的流程中会执行这些 task,对所有的 .class 文件(可包括第三方库的 .class)进行转换,转换的逻辑定义在 Transform 的 transform 方法中。实际上平时我们在 build.gradle 中常用的功能都是通过 Transform 实现的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)。
2、从所有class文件中将目标class文件找出来,并在目标class文件中的目标方法内插入目标字节码
也就是找出activity对应的class文件,并在该文件中找到onCreate方法,然后在方法内插入打印一行日志的字节码
这一步通过ASM来实现
具体的代码怎么写就不贴了,课程里有源码,个人认为理清思路比记住源码更重要,也更容易记住。虽然代码也很重要,但记忆力是有限的,特别是像咱这种“老年人”,只好选择性地记一些东西。
由于水平有限,如果文中存在错误之处,请大家批评指正,欢迎大家一起来分享、探讨!
博客:http://blog.csdn.net/MingHuang2017
GitHub:https://github.com/MingHuang1024
Email: MingHuang1024@foxmail.com
.com/MingHuang1024](https://github.com/MingHuang1024)
Email: MingHuang1024@foxmail.com
微信:724360018