吹爆系列:Android 插桩之美,全面掌握~

插桩

插桩是什么在开发中有用过插桩的技术吗/p>

所谓的插桩就是在代码编译期间修改已有的代码或者生成新代码。

类似 AndroidAnnotation/APT(Annotation Processing Tool),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。 这些代码生成的场景,它们生成的都是 Java 文件,是在编译的最开始介入。典型的有 Greendao、ButterKnife

对于代码监控、代码修改以及代码分析这三个场景,一般采用操作字节码的方式。可以操作“.class”的 Java 字节码,也可以操作“.dex”的 Dalvik 字节码,这取决于我们使用的插桩方法,相对于 Java 文件方式,字节码操作方式功能更加强大,应用场景也更广,但是它的使用复杂度更高。

Java 字节码

对于 Java 平台,Java 虚拟机运行的是 Class 文件,内部对应的是 Java 字节码。

使用火箭兔进行增量编译示例:

类似于钉钉、飞书这类办公软件,都有防截图的需求。现在甲方就需求在 APP 的所有 activity 以及 dialog 上加上全局水印,例如带上员工的名称工 等,一个 APP 常规来讲页面加上弹窗可能有上百个。好,我们人多加班加点干,但是应用内的第三方的 Activity ,和 Dialog 你怎么办呢/p>

ASM

我们先对 ASM 的三个重要的角色有个了解,他们分别是:

  • ClassReader
  • ClassVisitor
  • ClassWirter

ClassReader

我们通过上文的内容大概了解到,ASM 插桩对字节码进行修改。这肯定有个读取 Class 字节码的过程。那么 ClassReadr 就是这个读取器,他提供了对字节码的读取的方法。

ClassVisitor

ClassVisitor 是 ASM 插桩的核心,因为字节码的插桩修改就是在这一个步骤进行。ClassVisitor 是基于 访问者模式

ClassWirter

顾名思义,这应该是 ASM 提供的对字节码修改完以后,将修改完的内容进行写入的工具。写入的对象包括上面读取的对象都可以是字节码数组或者他们包装了一层的字节码流(Strem)

了解了上面的 ASM 的三个核心 API 的左右下面我们来进行一个小案例的使用实践:

ASM API 实践

我们在排查一些问题的时候,可能需要去根据某个方法的耗时时间来做定位和判断。一个或者几个方法我们可以通过像上文一样手动去编写。但是我需要给数百个方法或者整个应用所有的方法全部添加这个时候靠人力手动是不现实的,这个时候如何去解决/p>

依赖包引入
读取目标字节流
插入

我们先看一段伪代码或者是未完成的代码:

方法的构造、入口方法、b 方法一目了然,但是 ClassVisitor 只是提供了对类里面的元素的方法,我想具体的将我们自己的代码插入方法体怎么做呢/p>

我注意到 visitMethod 的返回值是 MethodVisitor ,没有错就是它:

查看一下里面和 method 相关的方法

唉~ 是我们想要的效果。铺垫了这么多,我们现在终于可以开始进行字节码插桩的修改了:

我们在 enter 想要插入的是:

在 exit 想要插入的是:

我们来看下完整的字节码指令:

class com/thunder/asmdemo/zxm31/Printer {  // access flags 0x0  init>()V    ALOAD 0    INVOKESPECIAL java/lang/Object.init> ()V    RETURN    MAXSTACK = 1    MAXLOCALS = 1  // access flags 0x9  public static main([Ljava/lang/String;)V    // parameter  args    INVOKESTATIC com/thunder/asmdemo/zxm31/Printer.b ()V    RETURN    MAXSTACK = 0    MAXLOCALS = 1  // access flags 0xA  private static b()V    TRYCATCHBLOCK L0 L1 L2 java/lang/InterruptedException    INVOKESTATIC java/lang/System.currentTimeMillis ()J    LSTORE 0   L0    LDC 2000    INVOKESTATIC java/lang/Thread.sleep (J)V   L1    GOTO L3   L2    ASTORE 2    ALOAD 2    INVOKEVIRTUAL java/lang/InterruptedException.printStackTrace ()V   L3    INVOKESTATIC java/lang/System.currentTimeMillis ()J    LSTORE 2    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;    NEW java/lang/StringBuilder    DUP    INVOKESPECIAL java/lang/StringBuilder.init> ()V    LDC "executed time : "    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;    LLOAD 2    LLOAD 0    LSUB    INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;    LDC " :ms"    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;    INV

                                                        

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年3月19日
下一篇 2022年3月19日

相关推荐