Android Gradle之Java字节码
Posted 好人静
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Gradle之Java字节码相关的知识,希望对你有一定的参考价值。
前言
逐步整理的一系列的总结:
Android 自定义Gradle插件的Extension类(五)
android Gradle之Java字节码(七)
Android Gradle 中的使用ASMified插件生成.class的技巧(九)
Android Gradle 中的实例之动态修改AndroidManifest文件(十)
一个Java源码文件经过javac命令编译成.class文件,Java源码文件中的内容被编译成了JVM可以识别的字节码指令。Java字节码文件即.class文件就是JVM(java虚拟机)能够识别可执行的指令文件,而.dex文件就是Android Dalivk虚拟机可以识别执行的指令文件。而下图是一个Java程序运行过程示意图:
从这个图中可以看到.class文件经过类加载子系统将具体实现功能加载到运行时数据区域。
一 JVM
JVM即Java虚拟机。JVM包括四部分内容:类加载子系统Class loader、执行引擎Execution Engine、运行时数据区域Runtime data area、本地库接口Native Interface。
1.类加载子系统Class loader
.class文件在JVM运行的时候,首先会经过类加载子系统完成类的加载到运行时数据区域Runtime data area。该过程包括查找和装载类定义(.class)信息、连接和类初始化过程。
2.执行引擎Execution Engine
可以理解为CPU,是JVM的核心部分,包括解释器、编译器以及垃圾回收(GC)。
- 解释器、编译器:主要用来执行.class中的字节码指令。主要有两种执行方式:解释执行和编译执行:
- 解释执行:在执行时翻译成虚拟机指令执行
- 编译执行:在执行之前先进行编译在执行
- 垃圾回收:自动管理运行时数据区的内存,将无用的内存进行清除,释放内存资源。主要清除的是堆里面的内存。
3.本地库接口Native Interface
与Native Library交互,与其他语言进行交互的接口,如C/C++方法的调用。
4.运行时数据区域Runtime data area
又称为内存区域。根据管理的内存划为若干个不同的数据区域,在JVM初期运行的时候就会分配方法区和堆,每创建一个线程就会为该线程分配私有的程序计数器、JVM栈和本地方法区,该三部分与线程的生命周期相同。
运行时数据区的五部分内容具体如下:
- (1)程序计数器(Program Counter Register)
当前线程正在执行的字节码指令的地址,即执行的字节码到了哪一行。内存最小的一块内存。线程私有。
如果执行的是Native方法,则该值为空(Undefined)。
唯一没有任何异常抛出的区域。
- (2)JVM栈(VM Stack)
执行Java方法的内存模型。线程私有。
每创建一个线程,就会分配一个栈。在线程中每执行一个方法,就会创建一个栈帧,把栈帧压入栈中;当方法返回或者抛出异常的时候,此栈帧出栈,JVM就会丢弃此栈帧。在活动线程中,只有位于栈顶的栈帧才有效,在执行引擎运行的时候,只针对当前栈帧进行操作(这个感觉类似于Davlik对Activity的Standard启动模式下在栈中的管理)。
栈帧又包括局部变量表(Local Variable Table)、操作数栈(Operand Stack)、返回地址(Return Address)、动态链接(Dynamic Link):
1⃣️局部变量表:方法执行过程中的所有变量。包括this指针(仅对实例方法、静态方法无)、方法的所有输入参数、方法中开辟的局部变量。在编译阶段完成分配,运行阶段不会改变数量。
2⃣️操作数栈:操作变量的内存模型。在编译阶段已经将最大深度写入在Code属性的max_stacks。方法刚执行时,该操作栈是空的,在方法执行的过程中,各种字节码往栈中存取数据:例如从局部变量表中复制变量写入到操作栈,方法执行完时,将变量出栈到局部变量表或者将返回值返回给方法。一个方法在执行期间会有多个出入栈操作;
3⃣️返回地址:遇到返回的字节码指令或者遇到没有处理异常的时候,方法退出。如果有返回值,则将返回值压入到调用者的栈帧中的操作数栈中;(遗留问题:还需要在理解下)
4⃣️动态链接:每个栈帧指向运行时常量池中该栈帧所属的方法的引用。而动态链接就是将符号所表示的方法之间转换成方法的直接引用 。(遗留问题:还需要在理解下)
四者关系如图:
- (3)本地方法栈(Native Method Stack)
调用Native方法的内存模型。线程私有
相比较于JVM栈是JVM执行Java方法服务的,那么本地方法栈就是为线程调用Native方法的时候服务。
出现异常的时候会抛出StackOverflowError和OutOfMemoryError
- (4)方法区(Method Area)
存储类信息、常量、静态变量、编译后的代码(即字节码)等。线程共享。
运行时常量池:存放的是编译期间生成的各种字面量和符号引用。
- (5)堆(Heap)
存放的是Java对象。线程共享。
内存中最大的一块,在JVM启动的时创建。几乎所有的对象实例都会在这里分配内存。同时也是垃圾收集器管理的主要的内存区域,又称为GC堆。
如果堆中没有内存完成实例分配,并且堆也无法扩展的时候,抛出OutOfMemoryError。
不同的内存区域存放的数据类型
- 堆:几乎所有的对象实例以及基本数据类型的成员变量
- JVM栈:基本数据类型的局部变量以及对象的引用变量
- 方法区:类相关的信息、常量、静态变量、编译后的代码(即字节码)。
内存溢出:在申请内存的时候,没有足够的内存空间供其使用
- JVM栈溢出:方法调用、方法内的局部变量都是在栈空间申请的,如果不够则抛出StackOverflowError
- 堆溢出:对象的内容都是在堆空间申请,如果不够抛出OutOfMemoryError
5.JVM的内存模型JMM
在JVM中,所有的线程中共享的内存称为主内存,用来存储除局部变量和方法参数之外的所有变量;每个线程都有自己独立的工作内存,保存的仅是共享变量的主内存副本,对变量的所有操作都必须在工作内存中,不能直接读写主内存的变量。这个主内存和工作内存和前面提到的运行时数据区域划为的五部分并不是一个层次的划分,两者基本上没有什么联系,若是非要多个对应关系的话,勉强可以将主内存与方法区、堆对应,工作内存与JVM栈、本地方法栈和程序计数器对应。
在Java的内存模型JMM中两个线程在交换数据的时候,采用的是共享内存的方式,存在8种操作:
- 1)lock:锁定。作用于主内存的变量,把变量标示为一条现场独占;
- 2)unlock:解锁。作用于主内存的变量,释放被lock的变量;
- 3)read:读取。作用于主内存的变量,把一个变量从主内存传输到线程的工作内存
- 4)load:载入。作用于工作内存的变量,把read操作从主内存中得到的变量值放入到工作内存的变量副本中;
- 5)use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎;
- 7)assign:赋值。作用于工作内存的变量,把执行引擎接收的值赋值给工作内存的变量
- 8)store:存储。作用于工作内存的变量,把工作内存中的值传递到主内存
- 9)write:写入。作用于主内存的变量,把工作内存得到的值放入到主内存的变量
二 从一个实例去看字节码文件
简单的总结一个.java文件经过编译之后生成.class文件的一些信息,其中.java文件如下:
public class ASMByte implements View.OnClickListener
private int a = 10;
private static int c = 20;
private int sum(int aa, int bb)
return aa + bb;
@Override
public void onClick(View v)
进入到编译之后的.class文件所在的目录,通过javap -v -p ASMByte.class可以查看.class文件的内容(每个.class文件对应着一个Classfile结构体信息)包括下面几个部分内容:
1..class文件的基本信息
描述的是.class文件的一些基本信息:如修改时间、大小、类名、版本号、权限标记符等信息。
Classfile /Users/j1/Documents/android/code/studio/AndroidPlugin/app/build/intermediates/javac/huaweiDebug/classes/com/android/androidplugin/ASMByte.class
Last modified 2021-8-6; size 801 bytes
MD5 checksum 36cbba70075af04c07ad0ade7dedb57a
Compiled from "ASMByte.java"
public class com.android.androidplugin.ASMByte implements android.view.View$OnClickListener
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
- (1)minor指的是jdk的次级版本号
此时0代表 “0.”;major 指的是jdk的初级版本号 。此时52代表“1.8”。那么结合major和minor,说明此时jdk的版本号是1.8.0。
- (2)flags指的是权限标志符
ACC_PUBLIC指的就是一个public类型的类;而ACC_SUPER指的是继承或实现了接口,默认的java会给所有的类去继承java.lang.Object。具体这些字符串代码的含义,后面结合着字节码框架在去详细总结下(遗留问题:补充链接)。
2.常量池
包含类中所有描述信息,用来识别.class文件中的所有数据。存放文字字符串、常量值、当前类名、字段名、方法名、各个字段和方法描述符、对当前类的字段和方法的引用信息、当前类中对其他类的引用信息等。
Constant pool:
#1 = Methodref #5.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#30 // com/android/androidplugin/ASMByte.a:I
#3 = Fieldref #4.#31 // com/android/androidplugin/ASMByte.c:I
#4 = Class #32 // com/android/androidplugin/ASMByte
#5 = Class #33 // java/lang/Object
#6 = Class #35 // android/view/View$OnClickListener
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 c
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lcom/android/androidplugin/ASMByte;
#17 = Utf8 sum
#18 = Utf8 (II)I
#19 = Utf8 aa
#20 = Utf8 bb
#21 = Utf8 MethodParameters
#22 = Utf8 onClick
#23 = Utf8 (Landroid/view/View;)V
#24 = Utf8 v
#25 = Utf8 Landroid/view/View;
#26 = Utf8 <clinit>
#27 = Utf8 SourceFile
#28 = Utf8 ASMByte.java
#29 = NameAndType #10:#11 // "<init>":()V
#30 = NameAndType #7:#8 // a:I
#31 = NameAndType #9:#8 // c:I
#32 = Utf8 com/android/androidplugin/ASMByte
#33 = Utf8 java/lang/Object
#34 = Class #38 // android/view/View
#35 = Utf8 android/view/View$OnClickListener
#36 = Utf8 OnClickListener
#37 = Utf8 InnerClasses
#38 = Utf8 android/view/View
下面就大体的总结下每个字符串代表什么意思。
(1)Methodref
Constant pool:
#1 = Methodref #5.#29 // java/lang/Object."<init>":()V
#1 = Methodref 指的就是一个java方法的引用,其后面的#5.#29代表的是后续具体的内容在常量池的#5和#29的位置,然后看下#5和#29的内容如下:
#5 = Class #33 // java/lang/Object
#29 = NameAndType #10:#11 // "<init>":()V
#5 = Class 又指向了#33的位置,#29 = NameAndType指向了#10:#11的位置 (从后面的注释中也能知道改行具体表示的内容)
#10 = Utf8 <init>
#11 = Utf8 ()V
#33 = Utf8 java/lang/Object
那么这个对应的该类的默认无参数的构造函数。
(2)Fieldref
#2 = Fieldref #4.#30 // com/android/androidplugin/ASMByte.a:I
#3 = Fieldref #4.#31 // com/android/androidplugin/ASMByte.c:I
接下来的两个 Fieldref指向的是该类的成员变量属性,后面的描述中指向了#4.#30和#4.#31的位置:
#4 = Class #32 // com/android/androidplugin/ASMByte
#30 = NameAndType #7:#8 // a:I
#31 = NameAndType #9:#8 // c:I
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 c
其中 #30= NameAndType中的NameAndType代表这是一个类的成员类型。对于 #2 = Fieldref后续对应具体位置为#4(对应#32)、#30(对应#7、#8),那么对应着com/android/androidplugin/ASMByte.a:I;
同理#3 = Fieldref就对应着 com/android/androidplugin/ASMByte.c:I。
(3)Class
#4 = Class #32 // com/android/androidplugin/ASMByte
#5 = Class #33 // java/lang/Object
#6 = Class #35 // android/view/View$OnClickListener
代表的是一个Class,具体后续的位置位于#27、#28,如下:
#32 = Utf8 com/android/androidplugin/ASMByte
#33 = Utf8 java/lang/Object
#35 = Utf8 android/view/View$OnClickListener
而这个Utf8的描述其实就是代表一个Utf-8的字符串,具体内容就是后面的这个字符串。
那么这个#4和#5就代表了两个类com/android/androidplugin/ASMByte和java/lang/Object的描述。
(4)Utf8
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 c
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
在前面(3)Class中也提到了,代表就是一个Utf-8的字符串,具体的内容就是后面对应的这个字符串。
(5)NameAndType
#29 = NameAndType #10:#11 // "<init>":()V
#30 = NameAndType #7:#8 // a:I
#31 = NameAndType #9:#8 // c:I
在前面的(2)Fieldref中也提到了,代表的就是一个该类的成员变量的描述。
从上面可以看到, 在.class文件中,存储占比最大的就是常量池,包含了.class文件中的所有字符串字典。
3.类的代码相关信息
在常量池后面就是的内容就是该类的具体的代码信息:例如类中的属性引用、方法引用。
(1)属性引用
private int a;
descriptor: I
flags: ACC_PRIVATE
private static int c;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
其实就是描述了该类的属性的对应的字段信息。对应的就是下面java代码:
private int a = 10;
private static int c = 20;
(2)方法引用
下面依次对应的就是该类的默认的构造函数ASMByte()、sum()以及???
public com.android.androidplugin.ASMByte(); //默认的构造函数
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 10
7: putfield #2 // Field a:I
10: return
LineNumberTable:
line 10: 0
line 11: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/android/androidplugin/ASMByte;
private int sum(int, int);
descriptor: (II)I
flags: ACC_PRIVATE
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 15: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/android/androidplugin/ASMByte;
0 4 1 aa I
0 4 2 bb I
MethodParameters:
Name Flags
aa
bb
static ;
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 20
2: putstatic #3 // Field c:I
5: return
LineNumberTable:
line 12: 0
第一个 public com.android.androidplugin.ASMByte(); 显然就是默认的构造函数 ASMByte() 。 对比该private int sum(int, int);Java源码的方法经过编译之后生成.class字节码之后的结构,对比如下:
- (1)private int sum(int, int);标记sum()方法的开始;
- (2)descriptor:表示该方法的签名,以"(传入参数类型)返回值类型"的方式返回。在字节码框架如ASM中有对应的方法及字段获取到该签名;
- (3)flags:方法的修饰符。在字节码框架如ASM中有对应的方法及字段获取到修饰符。
- (4)Code:标示该方法的具体实现的内容的开始:
- 1)stack:该方法在执行过程中需要的最大操作数栈深度。JVM是基于前面提到的解释执行,在执行引擎执行的时候,才会把对应的指令翻译成可以识别的指令,也可以理解为包含的java指令数。
- 2)locals:局部变量表中的变量个数:包括传入参数、局部变量和默认的this变量,详见下面的LocalVariableTable。如果静态方法,则无this变量。
- 3)args_size:方法的传入参数个数,非静态方法会默认的传入this作为参数。
- 4)0:iload_1~3:ireturn3:方法执行的指令集。在该方法中局部变量表中共有3个变量。当执行该方法的时候,需要将局部变量表中的值赋值到操作栈中:
- iload_1:把索引值为1的局部变量放入到操作数栈中
- iload_2:把索引值为2的局部变量放入到操作数栈中
- iadd:把操作数栈中的两个值相加
- ireturn:将操作数栈中的值返回给方法
- 5)LineNumberTables:源代码所在的行数集合。
- 6)LocalVariableTable:局部变量表中的所有变量的集合
补充一些常见的指令集:
- 1.加载和存储指令:将局部变量表中的值和操作数栈来回传输。
- (1)iload(_n)(n为第n个值)、lload(_n)(n为第n个值)、float(_n)(n为第n个值)、dload(_n)(n为第n个值)、aload(_n)(n为第n个值)将局部变量表中的int、long、float、double、对象类型的数值加载到操作数栈
- (2)istore(_n)(n为第n个值)、lstore(_n)(n为第n个值)等:将操作数栈的对应类型的数值存储到对应的局部变量表
- (3)bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_n(n为第n个值)、lconst_n(n为第n个值):将常量加载到操作数栈
- (4)若将一个int值压入到操作栈的时候,不同的int值的取值范围对应不同的指令:当int取值为-1~5时采用iconst、-128~127采用bipush、-32678~32767采用sipush、-2147483648~2147483647采用 ldc :将常量值加载到操作数栈:例如iconst_5就是将5压入到操作数栈;bipush 127就是将127压入到操作栈;
- 2.算术指令
- 如iadd、isub、imul等
- 3.对象创建与访问指令
- (1)new:创建实例
- (2)newarray、anewarrary、multianewarray:创建数组
- (3)getstaic:获取指定的静态域,并将其压入栈顶;putstatic:为指定的静态域赋值;getfield:访问指定的字段;putfield
- (4)baload、caload、saload、aaload等:把数组元素加载到操作数栈中
- (5)bastore、castore等:把操作栈中的数值存储到数组元素中
- (6)arraylength:获取数组长度
- (7)instanceof、checkcast:检查实例类型
- 4.操作数栈管理指令:
- (1)pop(n)(n为元素个数):将操作数栈中的栈顶的n个元素出栈
- (2)dup(n)(n为元素个数)、dup(n)(n为元素个数)_x(m)(m为复制的次数):复制栈顶的n个数值并且复制m次重新压入栈顶
- (3)swap:将最栈顶的两个数值互换
- 5.方法调用和返回指令
- (1)invokevirtual:调用对象的实例方法
- (2)invokeinterface:调用接口方法
- (3)invokespecial:调用需要特殊处理的方法,包括实例初始化方法、私有方法和父类方法
- (4)invokestatic:调用类方法
- (5)MethodParameters:方法的传入参数集合。若无传入参数,该字段无
注意最后一部分的 static ;是当类中存在用static修饰的静态类型字段,或static块,编译器便会生成<clinit> 。
这样就解析完一个Java方法转换成.class字节码的整个过程 。
4.附加属性集合
SourceFile: "ASMByte.java"
InnerClasses:
public static #36= #6 of #34; //OnClickListener=class android/view/View$OnClickListener of class android/view/View
用来记录生成该.class文件的源码文件的名称。
三 Android Studio查看字节码文件
Android Studio默认打开的.class文件看到的并不是字节码文件,需要安装一个ASM Bytecode Viewer插件。在Android Studio->Preferences-<Plugins中搜索ASM Bytecode Viewer安装,如图所示:
重启Android Studio,找到需要打开的.class文件,右击就会出现ASM Bytecode Viewer的选项,如图所示
打开该文件内容如下:
// class version 52.0 (52)
// access flags 0x21
public class com/android/androidplugin/ASMByte implements android/view/View$OnClickListener
// compiled from: ASMByte.java
// access flags 0x609
public static abstract INNERCLASS android/view/View$OnClickListener android/view/View OnClickListener
// access flags 0x2
private I a
// access flags 0xA
private static I c
// access flags 0x1
public <init>()V
L0 //该标签标记的是方法的字节码中的位置
LINENUMBER 12 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 13 L1
ALOAD 0
BIPUSH 10
PUTFIELD com/android/androidplugin/ASMByte.a : I
RETURN
L2
LOCALVARIABLE this Lcom/android/androidplugin/ASMByte; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x2
private sum(II)I
// parameter aa
// parameter bb
L0
LINENUMBER 17 L0
ILOAD 1
ILOAD 2
IADD
IRETURN
L1
LOCALVARIABLE this Lcom/android/androidplugin/ASMByte; L0 L1 0
LOCALVARIABLE aa I L0 L1 1
LOCALVARIABLE bb I L0 L1 2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0x1
public onClick(Landroid/view/View;)V
// parameter v
L0
LINENUMBER 23 L0
RETURN
L1
LOCALVARIABLE this Lcom/android/androidplugin/ASMByte; L0 L1 0
LOCALVARIABLE v Landroid/view/View; L0 L1 1
MAXSTACK = 0
MAXLOCALS = 2
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 14 L0
BIPUSH 20
PUTSTATIC com/android/androidplugin/ASMByte.c : I
RETURN
MAXSTACK = 1
MAXLOCALS = 0
四 总结
经过这些简单的总结,对Java字节码只是有了一点点简单的认知,后面结合着字节码框架在实现字节码插桩的时候,再回过头来在总结下:
- 1..class文件是JVM可以识别运行的指令文件;而.dex文件是Android Dalivk虚拟机可以识别的指令文件;
- 2.通过Android Gradle的Transform可以实现在.class文件转换成.dex文件的时候,通过字节码框架对.class文件进行插桩;
- 3.一个工程的Java源代码经过javac转换成了.class文件,而.class文件运行在JVM的时候,会首先通过类加载子系统对.class文件进行加载和类的初始化;
- 4.一个JVM(Java虚拟机)主要分为四部分:类加载子系统、运行时数据区域、执行引擎和本地库接口;
- 5.类加载子系统主要就是负责将.class文件加载到JVM,并且实现类的加载以及初始化
- 6.执行引擎是核心部分:执行字节码指令以及垃圾回收;
- 7.本地库接口:就是用来实现跨语言调用
- 8.运行时数据区域又包括五部分内容:JVM栈、操作数栈、本地方法栈、方法区以及堆;
- 9.JVM栈、操作数栈以及本地方法栈是在线程创建的时候,才会创建相应的区域,随着线程的生命周期而创建和销毁、线程私有;
- 10.方法区和堆是JVM在运行时就会创建,线程共享;
- 11.当线程每运行一个方法,就会为该方法创建一个栈帧,压入到JVM栈中;当方法执行完或者异常抛出的时候,此栈帧就会出栈,JVM丢弃此栈;
- 12.每个栈帧又包括:局部变量表、操作数栈、返回地址以及动态链接;
- (1)局部变量表就是存放的方法执行过程中的所有变量:输入参数、局部变量以及this指针(仅存在非静态方法)。在编译阶段就会分配完所有的变量,运行期间不会变化;
- (2)操作数栈主要用来操作变量。方法在执行过程中,会将局部变量表中的变量进行不断的出入栈;
- (3)返回地址:方法需要返回的时候,就会将最后的结果压入到调用者的栈帧操作数栈中;
- (4)动态链接:每个栈帧指向运行时常量池中该栈帧所属的方法的引用,而动态链接就是将符号表示的方法转换成方法的直接引用;
- 13.方法区:存放着类信息、常量、静态变量、编译之后的代码等
- 14.堆:存放着所有对象实例。同时也是垃圾回收器管理的主要区域;
- 15.JVM中的线程在传递数据的时候通过共享内存的方式。每个工作内存中存储的是共享变量的副本,所有对变量的操作都需要在工作内存中执行,而共享变量是存放在主内存中;
这次总结的有些地方还是有点模糊,例如在后面做字节码插桩的时候,我到底应该怎么去通过字节码框架来修改.class文件呢?一般都是修改.class文件的哪些内容?那如果我新增一个方法的时候,是不是就需要把一个方法包括的那些字节码都要计算和添加呢:例如操作数栈的操作、?
以上是关于Android Gradle之Java字节码的主要内容,如果未能解决你的问题,请参考以下文章
Android AOP编程——Gradle插件+TransformAPI+字节码插桩实战