字节码操纵框架ASM

引言

在我们实际的开发过程中,很多场景需要AOP的编程思想,在开发者无感知地侵入式的插如自己的业务逻辑,比如我最近做的一个埋点统计的一些场景,在开发者无感知情况下,将生命周期上报执行逻辑代码植入到我们现有的APP的某些页面的Class里面,将用户事件的逻辑代码植入到对应的事件响应方法里面。这里我们就引出了字节码操作框架ASM,通过ASM修改编译过程生成的java字节码,植入埋点上报的业务逻辑方法,从而不需要开发者在每个页面生命周期或者基类生命周期,事件响应方法里面做大量的埋点代码重复工作。

介绍

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

ASM效果展示

这是一个简单的埋点工程,在工程里面的页面MainActivity里面添加事件
在这里插入图片描述
通过ASM插件的加入,最终编译出来的Class字节码文件
在这里插入图片描述
但是在编译出来的class字节码文件里面多出了一行代码,这就是植入的一行埋点统计方法

  ...
  this.findViewById(2131427418).setOnClickListener(new OnClickListener() {
              public void onClick(View var1) {
                  //多出来的代码,即工具植入的埋点方法代码 
                  Monitor.INSTANCE.onViewClick(var1);
                  Log.d(MainActivity.TAG, "onclick btn_test1 ");
              }
          });
  ...

看到这,想必大家就见识到ASM黑科技的威力了,通过ASM可以对java字节码进行各种修改,达到我们所需要的目的。(ps:很多流氓sdk的广告植入大部分都是采用类似的方案)

ASM初步使用
  • 1.工程引入
dependencies {
    ...
    compile 'org.ow2.asm:asm:5.2'
    compile 'org.ow2.asm:asm-commons:5.2'
}

  • 2.调用ASM的类读写器
    ClassReader classReader = new ClassReader(file.bytes)
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    ClassVisitor cv = new ClickEventVisitor(classWriter)
    classReader.accept(cv, ClassReader.EXPAND_FRAMES)
    byte[] code = classWriter.toByteArray()
    FileOutputStream fos = new FileOutputStream(file.absolutePath)
    fos.write(code)
    fos.close()
  • 3.实现ClassVisitor植入埋点代码
class ClickEventVisitor extends ClassVisitor {

    ClickEventVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv)
    }

    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        def methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions)
        methodVisitor = new AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, desc) {
            boolean isInject() {
                return name == "onClick"
            }
            @Override
            protected void onMethodEnter() {
                super.onMethodEnter()
                //判断是否需要植入代码
                if (!isInject()) {
                    return
                }
                //这里就是我们植入的一段埋点代码 即调用Monitor.INSTANCE.onViewClick方法
                mv.visitFieldInsn(GETSTATIC, "com/zgkxzx/malfurion/Monitor", "INSTANCE", "Lcom/zgkxzx/malfurion/Monitor;")
                mv.visitVarInsn(ALOAD, 1)
                mv.visitMethodInsn(INVOKEVIRTUAL, "com/zgkxzx/malfurion/Monitor", "onViewClick",
                        "(Landroid/view/View;)V", false)

            }
        }
        return methodVisitor
    }
}

在 ASM 中,提供了一个 ClassReader类,这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码。它会调用 accept方法,这个方法接受一个实现了 ClassVisitor接口的对象实例作为参数,然后依次调用 ClassVisitor接口的各个方法。字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader知道如何调用各种 visit 函数。在这个过程中用户无法对操作进行干涉,所以遍历的算法是确定的,用户可以做的是提供不同的 Visitor 来对字节码树进行不同的修改。ClassVisitor会产生一些子过程,比如 visitMethod会返回一个实现 MethordVisitor接口的实例,visitField会返回一个实现 FieldVisitor接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。因此对于 ClassReader来说,其内部顺序访问是有一定要求的。实际上用户还可以不通过 ClassReader类,自行手工控制这个流程,只要按照一定的顺序,各个 visit 事件被先后正确的调用,最后就能生成可以被正确加载的字节码。当然获得更大灵活性的同时也加大了调整字节码的复杂度。

初步使用小节只是ASM的初步引入基本使用,后续章节会深入字节码,对ASM的几个重要的接口
1.ClassVisitor 类访问器
2.FieldVisitor fang 变量访问器
3.MethodVisitor 方法访问器
4.AnnomatioinVisitor 注解访问器
进行深入讲解和使用

ASM植入过程分析

正常的java IDE在对java文件编译后运行的流程(ps:这里的图用的经典的HelloWorld做编译分析,更容易理解)
在这里插入图片描述

在使用我们自己的插件后,整个IDE工程的编译流程
在这里插入图片描述
可以看出,在javac将程序员编写的java文件编译成class字节码文件后,myPlugin插件工具对生成的class文件进行植入式修改变成新的class文件,最后通过java指令执行class运行起来(Android通过ART虚拟机执行Dex文件,dex也是有这些class打包生成的),从而达到植入的目的。这里我们对比下刚才工程的植入的代码的字节码文件二进制对比
在这里插入图片描述可以看出,文件的二进制基本上一样,就是对文件下面的二进制进行了植入。我们在对比下字节码文件
在这里插入图片描述
对比可以很容易看出,注入的字节码文件对常量池进行添加,对方法的code属性进行了添加调用埋点统计的方法,从而无感知的实现了埋点的功能。

总结

ASM字节码操纵框架的出现为我们开发人员编写各种框架各种SDK工具实现更多的可能,本章只是简单的介绍和引入(内容比较多一章写不完)。后面会分小章进行ASM重要类方法的精讲。谢谢-

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值