编译插桩
编译插桩就是在代码编译期间修改已有的代码或者生成新代码。实际上,我们项目中经常用到的 Dagger、ButterKnife 甚至是 Kotlin 语言,它们都用到了编译插桩的技术。
我们可以在 1、2 两处对代码进行改造。
在 .java 文件编译成 .class 文件时,APT、AndroidAnnotation 等就是在此处触发代码生成。
在 .class 文件进一步优化成 .dex 文件时,也就是直接操作字节码文件,也是本课时主要介绍的内容。这种方式功能更加强大,应用场景也更多。但是门槛比较高,需要对字节码有一定的理解。
编译插桩实现功能
日志埋点;
性能监控;
动态权限控制;
业务逻辑跳转时,校验是否已经登录;
甚至是代码调试等。
插桩工具
AspectJ
AspectJ 是老牌 AOP(Aspect-Oriented Programming)框架,如果你做过 J2EE 开发可能对这个框架更加熟悉,经常会拿这个框架跟 Spring AOP 进行比较。其主要优势是成熟稳定,使用者也不需要对字节码文件有深入的理解。ASM
通过 ASM 可以修改现有的字节码文件,也可以动态生成字节码文件,并且它是一款完全以字节码层面来操纵字节码并分析字节码的框架
ASM 实现在每一个 Activity 打开时输出相应的 log 日志
思路
遍历项目中所有的 .class 文件
众所周知,Android Studio 使用 Gradle 编译项目中的 .java 文件,并且从 Gradle1.5.0 之后,我们可以自己定义 Transform,来获取所有 .class 文件引用。但是 Transform 的使用需要依赖 Gradle Plugin。因此我们第一步需要创建一个单独的 Gradle Plugin,并在 Gradle Plugin 中使用自定义 Transform 找出所有的 .class 文件。遍历到目标 .class 文件 (Activity)之后,通过 ASM 动态注入需要被插入的字节码
过滤出目标 Activity 文件,并在目标 Activity 文件的 onCreate 方法中,通过 ASM 插入相应的 log 日志字节码。
Transform
Transform 可以被看作是 Gradle 在编译项目时的一个 task,在 .class 文件转换成 .dex 的流程中会执行这些 task,对所有的 .class 文件(可包括第三方库的 .class)进行转换,转换的逻辑定义在 Transform 的 transform 方法中。
实际上平时我们在 build.gradle 中常用的功能都是通过 Transform 实现的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)。
getName:
设置我们自定义的 Transform 对应的 Task 名称。Gradle 在编译的时候,会将这个名称显示在控制台上。比如:Task :app:transformClassesWithXXXForDebug。
getInputType:
在项目中会有各种各样格式的文件,通过 getInputType 可以设置 LifeCycleTransform 接收的文件类型,此方法返回的类型是 Set<QualifiedContent.ContentType> 集合。
ContentType 有以下 2 种取值。
CLASSES:代表只检索 .class 文件;
RESOURCES:代表检索 java 标准资源文件。
getScopes()
这个方法规定自定义 Transform 检索的范围,具体有以下几种取值:
isIncremental()
表示当前 Transform 是否支持增量编译,我们不需要增量编译,所以直接返回 false 即可。
transform()
在 自定义Transform 中最重要的方法就是 transform()。在这个方法中,可以获取到两个数据的流向。
inputs:inputs 中是传过来的输入流,其中有两种格式,一种是 jar 包格式,一种是 directory(目录格式)。
outputProvider:outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做,否则编译会报错。
ASM
ASM 是一套开源框架,其中几个常用的 API 如下:
ClassReader:负责解析 .class 文件中的字节码,并将所有字节码传递给 ClassWriter。
ClassVisitor:负责访问 .class 文件中各个元素,ClassVisitor 就是用来解析这些文件结构的,当解析到某些特定结构时(比如类变量、方法),它会自动调用内部相应的 FieldVisitor 或者 MethodVisitor 的方法,进一步解析或者修改 .class 文件内容。
ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,负责将修改后的字节码输出为 byte 数组。
具体实现
- 创建类 LifeCyclePlugin.groovy 文件。在 LifeCyclePlugin 中重写 apply 方法,实现插件逻辑
- 修改 asm_lifecycle_plugin module 的 build.gradle
- 创建 properties 文件
- 自定义Transform,实现transform方法
- 将自定义的 LifeCycleTransform 注册到 Gradle 插件中
- 使用 ASM,插入字节码到 Activity 文件