Android Gradle插件平台开发系列:前言
Android Gradle插件平台开发系列一:Android APT
Android Gradle插件平台开发系列二:Android SPI
Android Gradle插件平台开发系列三:自定义gradle plugin
Android Gralde插件平台开发系列四:自定义Gradle Transform
Android Gralde插件平台开发系列五:字节码修改
Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
AspectJ 的作为一个老牌的插桩框架优点是 1 成熟稳定 2 使用简单。但是 AspectJ 的缺点是,由于其基于规则,所以其切入点相对固定,对于字节码文件的操作自由度以及开发的掌控度就大打折扣。还有就是如果我们要实现对所有方法进行插桩,代码注入后的性能也是我们需要关注的一个重要的点,我们希望只插入我们想插入的代码,而AspectJ会额外生成一些包装代码,对性能以及包大小有一定影响。
Javassist 源代码级 API 比 ASM 中实际的字节码操作更容易使用。Javassist 在复杂的字节码级操作上提供了更高级别的抽象层。Javassist 源代码级 API 只需要很少的字节码知识,甚至不需要任何实际字节码知识,因此实现起来更容易、更快。Javassist使用反射机制,这使得它比运行时使用 Classworking 技术的ASM慢。
相比 AspectJ,ASM 更加直接高效。但是对于一些复杂情况,我们可能需要使用另外一种 Tree API 来完成对 Class 文件更直接的修改,因此这时候你要掌握一些必不可少的 Java 字节码知识,ASM 的特点是功能强大操作灵活,但是上手的难度也会比 AspectJ 更难,但是它能获得更好的性能,更适合大面积的插桩场景。
ASM包含Tree API和Visitor API两种API类型,这里主要使用Visitor API。
Visitor API | 具体作用 |
---|---|
ClassReader | 解析编译过的.class文件,可以获取该文件中的类名、接口、成员名、方法参数等 |
ClassWriter | 重构编译之后的类,用来修改类名、属性、方法等以及生成新的.class文件 |
ClassVisitor | 访问类信息。包括标记在类上的注解、类构造方法、类字段、类方法、静态代码块 |
AdviceAdapter | 实现了MethodVisitor接口,主要访问方法的信息。用来对具体方法进行字节码操作 |
FieldVisitor | 访问具体的类成员 |
AnnotationVisitor | 访问具体的注解信息 |
implementation 'org.ow2.asm:asm:9.2'
implementation 'org.ow2.asm:asm-commons:9.2'
基于上节自定义Gradle Transform查找到的目标类,演示在MainActivity的onCreate方法后面打印一行日志。
val classReader = ClassReader(file.readBytes())
/**
* flag = 0, 必须自己计算栈帧、局部变量以及操作数堆栈的大小 ,也就是要自己调用visitmax和visitFrame方法。
* flag = ClassWriter. COMPUTE_MAXS,局部变量和操作数堆栈部分的大小会自动计算,需要自己调用visitFrame方法设置栈帧。
* flag = ClassWriter.COMPUTE_FRAMES,所有的内容都是自动计算的,不必调用visitFrame和visitmax。
*/
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
4.4.1 自定义ClassVisitor,关联AdviceAdapter。
class TestVisitor(api: Int, classVisitor: ClassVisitor?) : ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array?
): MethodVisitor {
val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
if (name.equals("onCreate")) {
println("method name-->" + name)
return TestAdapter(Opcodes.ASM9, methodVisitor, access, name, descriptor)
}
return methodVisitor
}
}
4.4.2 自定义AdviceAdapter,实现onMethodExit方法,完成代码插入逻辑处理。
class TestAdapter(
api: Int,
methodVisitor: MethodVisitor?,
access: Int,
name: String?,
descriptor: String?
) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {
override fun onMethodExit(opcode: Int) {
super.onMethodExit(opcode)
//System.out.println("测试ASM插入代码");
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("\u6d4b\u8bd5ASM\u63d2\u5165\u4ee3\u7801");
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
}
}
classReader.accept(classVisitor, Opcodes.ASM9)
fun insertCode(file: File) {
try {
// 1.创建ClassReader,读取目标类内容
val classReader = ClassReader(file.readBytes())
// 2.创建ClassWriter,关联ClassReader
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
// 3.创建ClassVisitor,关联ClassWriter
val classVisitor = TestVisitor(Opcodes.ASM9, classWriter)
// 4.ClassReader关联ClassVisitor
classReader.accept(classVisitor, Opcodes.ASM9)
// 5.获取生成类的二进制流并写回原路径
val code = classWriter.toByteArray()
val fos = FileOutputStream(file.absolutePath)
fos.write(code)
fos.close()
} catch (e: Exception) {
e.stackTrace
}
}
在app\build\intermediates\javac目录下可直接查看修改编译后的代码。
在app\build\tmp\kotlin-classes目录下,选中对应的类。
这时候会发现打开的类只有方法名却没有具体内容,需要使用Tools–>Kotlin–>Kotlin Bytecode工具查看。
可以使用ASM Bytecode Viewer转换java/kotlin代码,降低ASM开发难度。
Android ASM框架详解