Android 字节码插装介绍

481 阅读6分钟

字节码插装原理

字节码插装本质上是换了一种编程方式,最初大家是通过Java,kotlin等高级语言编程。运行在Java虚拟机上的语言并不是直接被虚拟机加载,而是要先通过javac,kotlinc编译器编译成后缀为.class的字节码文件后才能被虚拟机加载。

字节码插装是在编译完成后对生成的class文件进行二次操作。

Java虚拟机只与class文件打交道。class文件,也可以看作一种语言,相比于更贴近人类的高级语言,它的可读性更差。但也是具有语法和结构的。在Java中创建对象 或 声明一个变量等操作最终会由多条字节码指令组合完成。

总之呢字节码插装就是程序员面对class文件进行编程

举个小例子感受一下字节码指令

代码非常简单,两个静态方法,add()加法计算,append()字符串拼接。先使用Javac工具把Test.java编译为Test.class,在通过Javap查看Test.class的字节码指令

---------------------java 源文件-----------------------

public class Test {

    public static int add(int num1, int num2) {
        return num1 + num2;
    }

    public static String append(String str1, String str2) {
        return str1+str2;
    }
}

---------------------命令行操作-----------------------
javac Test.java

javap -c Test

---------------------字节码指令-----------------------

public class com.example.architecture.Test {
  public com.example.architecture.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static int add(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: iadd
       3: ireturn

  public static java.lang.String append(java.lang.String, java.lang.String);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: aload_0
       8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      11: aload_1
      12: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      18: areturn
}

字节码指令分析:

生成了三个方法,除自定义的add(),append()方法外,还有默认的构造方法

add方法比较简单

首先调用 iload_0 这是什么意思呢,从局部变量表第一个位置把数据加载到栈帧,在字节码中i表示int类型,还有d表示double,c表示char。

iload_1iload_0 一致

iadd 表示进行 加法运算

ireturn 返回int类型的数据

append方法稍微复杂一点

new 表示创建对象实例,后面跟着类的全限定名 在例子中创建了java/lang/StringBuilder

dup 复制栈顶数据并重新压入栈顶

invokespecial 用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和 父类方法

aload_0 a表示引用类型,把引用类型加入栈帧中,也就是刚刚创建的StringBuilder对象

invokevirtual 用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派), 这也是Java语言中最常见的方法分派方式,

指令相当于一个方法,在调用指令的同时还必须要告诉指令,我要调用的方法签名,结合后面注释的信息才是一次完整的通过指令调用方法,如下是调用StringBuilder.append 的玩则完整指令

  1. invokevirtual 字节码指令 调用对象的实例方法
  2. java/lang/StringBuilder.append 表明调用类的那个方法
  3. (Ljava/lang/String;) 方法参数 L表示引用类型 后跟类的全限定名
  4. Ljava/lang/StringBuilder; 方法返回值 规则与方法参数一致
invokevirtual java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 

方法签名的具体规则如下

Untitled.png

Android字节码插装准备

上一节体验了一下字节码相关的知识,字节码插装的本质就是换了一门语言写代码,由原来的Java,kotlin等高级语言,换为字节码这种更接近底层的语言。

Android字节码插装有两个前提

  1. 获取class文件
  2. 对字节码内容进行操作

在Android上使用字节码插装的第一个问题就是如何获取到class文件,测试情况下可以手动使用命令手动编译文件,但实际开发中这样做手动编译没有意义。

这里涉及到Android构建流程的知识,在打包apk的过程中有一步骤是将Java,kotlin源文件编译为class文件。

Android打包过程基于Gradle,Google官方实现自己的插件AGP,同时开放api允许开发者监听构建流程,干预编译过程,能够获取到编译后的class文件。

如何获取class文件的问题解决了。

对class文件进行操作,可以使用aspectJ,javassit,asm等三方框架,aspectJ 与 javassit对字节码操作封装程度较高使用起来比较容易。 asm的封装程度最低,需要一定的字节码相关知识,不过asm的性能最好。

在AGP7.0之前,使用transform API时,开发者可以任意选择三方框架。AGP7.0之后,transformAPI被废弃,8.0后被移除。

官方提供的新APIAsmClassVisitorFactory instrumentation ,相比于transform API,新api官方进行了封装,使用更简单,性能更高。但是与asm强关联,以后字节码插装只能使用asm框架,虽然使用麻烦一点,但性能更好,熟悉了字节码之后也就那样,而且有插件可以查看字节码辅助开发。

小总结:

Android字节码插装需要三个前置知识

  1. 了解Gradle知识,知道如何自定义插件
  2. 了解AGP监听构建流程,获取字节码文件相关api
  3. asm框架的使用

学习流程介绍(水一节)

原本想把新API各个类,整个流程讲讲,可惜能力有限,没有自己的思考扒别人的内容也没意思,就改为把学习插装用到资源介绍一下,学完看懂demo,上手肯定是没问题

视频,看完后asm怎么用应该是可以入门了

【Android进阶系统学习】:字节码插桩技术实现自动化方法耗时记录_哔哩哔哩_bilibili

Transform API介绍

其实 Gradle Transform 就是个纸老虎 —— Gradle 系列(4) - 掘金 (juejin.cn)

AGP新api AsmClassVisitorFactory 介绍,文末有大佬文章可看

刚学会Transform,你告诉我就要被移除了 - 掘金 (juejin.cn)

AsmClassVisitorFactory 实战,有demo

Transform 被废弃,ASM 如何适配? - 掘金 (juejin.cn)

Gradle相关知识,文末有大佬的系列文章都不错的 可看,Gradle其实挺复杂的弄懂概念需要一段时间

写给小白的Gradle指南 - 掘金 (juejin.cn)

Gradle插件

Gradle插件的使用 - 掘金 (juejin.cn)

Java基础

javac和javap - 掘金 (juejin.cn)

(33条消息) Java中的方法签名_Studious_Li的博客-CSDN博客_方法签名