前言
- 前一篇文章,我们讲了gradle插件从创建到执行的整体链路。
- 接下来我们来过一过字节码插桩的一些必备的知识点。
- 本系列的ASM插桩教程,是根据笔者自己探索得来的一些总结。因为也是在摸索中学习,所以有说的不对的地方,请大家评论指正。
- 或者在学习之余,大家一起交流交流。
ASM插桩基础知识
字节码
- 因为ASM是操作Java字节码的库,所以我们先介绍下Java字节码
- Java字节码是Java虚拟机(JVM)的指令集。每一条指令由一个字节的操作码和0个或者多个操作数组成。
- 简单说来,字节码就是javac编译后生成的.class文件中的字节指令。
- Java虚拟机解释执行的就是这些字节码文件。
- 具体的细节知识点,在这里就先不细讲了,毕竟目前笔者也还停留在使用(ASM)阶段,等后续有了更深了解,我们在单开文章来讨论。
- 具体的字节码文件,会是如下代码样式:
// class version 51.0 (51)
// access flags 0x21
public class com/muxi/gradleasmrock/RockApplication extends android/app/Application {
// compiled from: RockApplication.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL android/app/Application.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/muxi/gradleasmrock/RockApplication; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public onCreate()V
L0
LINENUMBER 9 L0
ALOAD 0
INVOKESPECIAL android/app/Application.onCreate ()V
L1
LINENUMBER 10 L1
RETURN
L2
LOCALVARIABLE this Lcom/muxi/gradleasmrock/RockApplication; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
}
- 其源码十分简单,如下:
package com.muxi.gradleasmrock;
import android.app.Application;
public class RockApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
}
}
常用概念
类名
字节码中对应类名,和java文件对应的类名是不一致的:
Java:
com.muxi.gradleasmrock.RockApplication
字节码:
com/muxi/gradleasmrock/RockApplication
变量
- 字节码中对应的基本数据类型和对象,也有不同的替代,如下表:
java | 字节码 |
---|---|
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
void | V |
Object | Ljava/lang/Object; |
int[] | [I |
Object[][] | [[Ljava/lang/Object; |
函数
- 函数也对应不同的表示:
java | 字节码 |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I)Ljava/lang/Object; |
- 上述概念,其实笔者也记不住,需要用的时候再查一查,用久了自然熟能生巧。
Gradle Transform初探
- 上面介绍完了字节码的常用概念,我们来看下如果需要进行ASM插桩的时候,需要提前做的一些准备工作
引用ASM
- ASM也是第三方库,使用dependencies依赖,就能很方便地引入。
- 我们在asmrock的module中,找到build.gradle,加入以下代码,就可以直接使用asm插桩库了:
dependencies {
implementation gradleApi()
implementation localGroovy()
//asm库之一
implementation 'org.ow2.asm:asm:7.2'
//asm库之一
implementation 'org.ow2.asm:asm-util:7.2'
//asm库之一
implementation 'org.ow2.asm:asm-commons:7.2'
//hunter是一个在asm基础上做了封装的三方库,能更方便我们使用asm插桩
implementation 'com.quinn.hunter:hunter-transform:0.9.3'
//gradle插件中需要访问Android工程相关的gradle配置,所以引入android的gradle插件版本
implementation 'com.android.tools.build:gradle:3.6.2'
implementation 'com.android.tools.build:gradle-api:3.6.2'
}
创建插件实现
- 引入asm库后,我们需要针对性地接管gradle的task,让gradle执行过程中,字节码生成后的task能流经我们自己的gradle插件。
- 因此,我们需要把我们的gradle插件注册到当前工程的编译Task任务中去。
- 这一系列操作,是gradle提供好的,我们只需要实现对应接口就好。
实现
- gradle提供了一个接口类:com.android.build.api.trasform.Transform
- Android编译过程中,实现了Transform接口的类,只要调用registerTransform()函数,就能将这个实现类注册到现有工程的gradle编译Task中。
- 此时,执行gradle编译,task任务就会流经这个Transform的实现类。
有点装饰器的意思。
- 我们先来创建一个Transform接口的实现类:
public class AsmRockCodeTransform implements Transform {
@Override
public String getName() {
return getClass().getName();//获取Transform的名称
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;//获取Transform消费的数据,比如Class,比如jar包
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;//获取Transform消耗的范围(整个project包含sub和三方内容 、或者当前project等)
}
@Override
public boolean isIncremental() {
return false;//是否增量任务,比如add/remove/change files,当有其他增量任务控制时,也会被影响,一般为false
}
}
- AsmRockCodeTransform需要实现三个函数,分别控制Transform的名称,作用数据、作用范围以及是否增量任务。
- 我们需要做的是遍历所有class文件,所以就按照上面注释所写,进行配置就好。届时,编译过程中所有的class文件都会流经我们的Transform。
- 当然,我们引入了Hunter库,其实Hunter库为我们实现Transform做了一些额外的封装,可以简化我们很多的操作。
- 因此,我们的AsmRockCodeTransform最终会使用Hunter库中的HunterTransform
public class AsmRockCodeTransform extends HunterTransform {
public AsmRockCodeTransform(Project project) {
super(project);
}
}
- HunterTransform其实就是实现了Transform接口,为我们封装了一层Abstract继承,使用还是基本一致的,感兴趣的同学可以自行撸源码,这里就不赘述了。
注册
- 创建好了Transform,接下来的工作就是将Transform注册到gradle的Task中了。
- 因为Transform本来就是Android的gradle编译提供的接口,所以我们注册也是使用Android的gradle编译提供的接口进行注册。
- 现在,我们回到之前写好的AsmRock类中。还记得这个类吧?我们前面说过在asm_rock.properties中指定的类就是插件的入口。
- AsmRock我们之前的实现是这样的:
public class AsmRock implements Plugin<Project> {
@Override
public void apply(Project project) {
System.out.println("Hello world!AsmRock!");
}
}
- 接下来,我们需要在插件被调用时,将我们需要做的Transform操作实现类,注册到Project中。
public class AsmRock implements Plugin<Project> {
@Override
public void apply(Project project) {
//AppExtension 就是 Android项目中的build.gradle下android对应的配置类:
// android {
// compileSdkVersion 29
// buildToolsVersion "29.0.2"
// defaultConfig {
// applicationId "com.muxi.gradleasmrock"
// minSdkVersion 23
// targetSdkVersion 29
// versionCode 1
// versionName "1.0"
// }
//}
// AppExtension是Android的gradle插件提供的,这就是为什么我们要在asm_rock的module中引用:
// implementation 'com.android.tools.build:gradle:3.6.2'
// implementation 'com.android.tools.build:gradle-api:3.6.2'
AppExtension appExtension = (AppExtension) project.getProperties().get("android");//获取android属性相关的配置类
if (appExtension != null) {
appExtension.registerTransform(new AsmRockCodeTransform(project));//获取到Android项目的配置类,Transform就是注册到AppExtension中的。
}
}
}
- 上述代码,注册完毕,在Android的编译过程中,就会自动执行AsmRockCodeTransform中的相应逻辑。
执行
- 注册成功以后,gradle编译task执行到AsmRockCodeTransform的时候,会自动回调AsmRockCodeTransform。
- 回调的函数,就是transform()函数
- 在Transform接口中,有两个transform函数:
public void transform(
@NonNull Context context,
@NonNull Collection<TransformInput> inputs,
@NonNull Collection<TransformInput> referencedInputs,
@Nullable TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException ;
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException ;
- 第一个已经被Deprecated了,但是其实还是会被回调的。ASM新版本推荐使用第二个transform(),其实逻辑上来说,两者都会被回调,所以使用谁都可以。
- HunterTransform实现的是Deprecated的transform,所以如果使用了HunterTransform的同学,覆写的就是第一个transform()。
- 当gradle回调transform的时候,就表示AsmRockCodeTransform到了执行时机,此时我们就可以在AsmRockCodeTransform中,做字节码插桩处理了。
- 照例,我们先来实际运行下看看效果:
public class AsmRockCodeTransform extends HunterTransform {
public AsmRockCodeTransform(Project project) {
super(project);
}
@Override
public void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
System.out.println("AsmRockCodeTransform execute");
}
}
- gradle插件,还是要被apply到Android的app工程中才能执行。
- 我们变更了gradle插件的代码,因此需要重新发布到本地的maven仓库,才能真正被执行起来。
./gradlew uploadArchives
- 执行上述命令,重新发布gradle插件代码,此时再去执行:
./gradlew assembleDebug
- 执行完毕后,就能看到AsmRockCodeTransform的输出:
小结
- 到这里,ASM操作字节码之前的工作基本都准备就绪了。下一步就是要使用ASM库具体的访问操作字节码。