Android Gralde插件平台开发系列五:字节码插桩

Android Gradle插件平台开发系列:前言

Android Gradle插件平台开发系列一:Android APT

Android Gradle插件平台开发系列二:Android SPI

Android Gradle插件平台开发系列三:自定义gradle plugin

Android Gralde插件平台开发系列四:自定义Gradle Transform

Android Gralde插件平台开发系列五:字节码修改

一、AOP概念

Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。

二、Android代码的编译过程以及插桩位置

Android Gralde插件平台开发系列五:字节码插桩_第1张图片

三、AOP方案对比

3.1 AspectJ

AspectJ 的作为一个老牌的插桩框架优点是 1 成熟稳定 2 使用简单。但是 AspectJ 的缺点是,由于其基于规则,所以其切入点相对固定,对于字节码文件的操作自由度以及开发的掌控度就大打折扣。还有就是如果我们要实现对所有方法进行插桩,代码注入后的性能也是我们需要关注的一个重要的点,我们希望只插入我们想插入的代码,而AspectJ会额外生成一些包装代码,对性能以及包大小有一定影响。

3.2 Javassist

Javassist 源代码级 API 比 ASM 中实际的字节码操作更容易使用。Javassist 在复杂的字节码级操作上提供了更高级别的抽象层。Javassist 源代码级 API 只需要很少的字节码知识,甚至不需要任何实际字节码知识,因此实现起来更容易、更快。Javassist使用反射机制,这使得它比运行时使用 Classworking 技术的ASM慢。

3.3 ASM

相比 AspectJ,ASM 更加直接高效。但是对于一些复杂情况,我们可能需要使用另外一种 Tree API 来完成对 Class 文件更直接的修改,因此这时候你要掌握一些必不可少的 Java 字节码知识,ASM 的特点是功能强大操作灵活,但是上手的难度也会比 AspectJ 更难,但是它能获得更好的性能,更适合大面积的插桩场景。

四、ASM具体用法

ASM包含Tree API和Visitor API两种API类型,这里主要使用Visitor API。

Visitor API 具体作用
ClassReader 解析编译过的.class文件,可以获取该文件中的类名、接口、成员名、方法参数等
ClassWriter 重构编译之后的类,用来修改类名、属性、方法等以及生成新的.class文件
ClassVisitor 访问类信息。包括标记在类上的注解、类构造方法、类字段、类方法、静态代码块
AdviceAdapter 实现了MethodVisitor接口,主要访问方法的信息。用来对具体方法进行字节码操作
FieldVisitor 访问具体的类成员
AnnotationVisitor 访问具体的注解信息

4.1 依赖ASM库

    implementation 'org.ow2.asm:asm:9.2'
    implementation 'org.ow2.asm:asm-commons:9.2'

4.2 创建ClassReader,读取目标类内容

基于上节自定义Gradle Transform查找到的目标类,演示在MainActivity的onCreate方法后面打印一行日志。

    val classReader = ClassReader(file.readBytes())

4.3 创建ClassWriter,关联ClassReader

    /**
     * flag = 0, 必须自己计算栈帧、局部变量以及操作数堆栈的大小 ,也就是要自己调用visitmax和visitFrame方法。
     * flag = ClassWriter. COMPUTE_MAXS,局部变量和操作数堆栈部分的大小会自动计算,需要自己调用visitFrame方法设置栈帧。
     * flag = ClassWriter.COMPUTE_FRAMES,所有的内容都是自动计算的,不必调用visitFrame和visitmax。
     */
    val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)

4.4 自定义ClassVisitor

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);
    }
}

4.5 ClassReader关联ClassVisitor

    classReader.accept(classVisitor, Opcodes.ASM9)

4.6 获取生成类的二进制流并写回原路径

    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
        }
    }

五、查看生成代码

5.1 java代码

在app\build\intermediates\javac目录下可直接查看修改编译后的代码。

Android Gralde插件平台开发系列五:字节码插桩_第2张图片

5.2 kotlin代码

在app\build\tmp\kotlin-classes目录下,选中对应的类。

Android Gralde插件平台开发系列五:字节码插桩_第3张图片

这时候会发现打开的类只有方法名却没有具体内容,需要使用Tools–>Kotlin–>Kotlin Bytecode工具查看。

Android Gralde插件平台开发系列五:字节码插桩_第4张图片

六、ASM Bytecode Viewer

可以使用ASM Bytecode Viewer转换java/kotlin代码,降低ASM开发难度。

Android Gralde插件平台开发系列五:字节码插桩_第5张图片

参考

Android ASM框架详解

你可能感兴趣的:(Android,Gradle平台开发系列,android,gradle)