- 这是一次给小白的ASM字节码插桩的入门逻辑。
- 由于研究了一下美团的Robust库的玩法,了解到了字节码插桩的一些基础知识,于是简单学习了一下ASM字节码插桩的使用。
- 这是我从上手开始进行插桩的整体链路过程,虽然只是初探,但是也确实学习到了不少东西。在这里给有兴趣的同学,做一个引入吧。
基础知识
- 在真的上手操作代码之前,其实我们还有一些基础的理论知识要做一做功课,先来了解一些必备的入门知识吧。
什么是字节码插桩
字节码插桩的思想,也可以说是AOP编程的一种实现方案。当然这是笔者自己这么理解的,如果觉得不对的同学求轻拍。
AOP就是大名鼎鼎的面向切面编程。简单来说就是从某一个切面切入来搞事情。比如说我们的Android app在编译过程中,是这样一张图:
- 比如当我们有这样一个需求:
- 在全工程所有的类的onCreate()函数输出一句Log。
- 上面的需求,当我们使用普通的编程方式时,我们需要手动地给全工程所有的onCreate()函数,手动加一句代码。
- 而当AOP的时候,我们使用ASM字节码插桩,在编译的过程中,执行完javac操作,生成了我们写好的代码编译成的字节码文件(.class)文件以后。
- 我们针对这些字节码文件(.class),横向地做一些特殊处理。
- 具体的实现可以抽象成这样:
- 我们在上图java Compiler的操作得到所有.class文件之后,切入管理这一操作。
- 遍历所有.class文件,使用ASM库针对性地将那句log代码插入到.class文件的onCreate()函数中。(具体实现我们后面慢慢聊)
- 从上面的描述来看,其实我们利用AOP最方便实现这样的需求。
- 因为此时我们就是使用面向切面变成的思想,在横向地针对工程中所有onCreate()函数进行操作。
- 而字节码插桩,就是具体实现这个操作的方式。
- 比如当我们有这样一个需求:
Gradle插件入门
- 我们使用ASM插桩时,是需要接管Gradle编译过程中,生成.class文件操作,对其进行拦截的。
- 因此,我们需要知道Gradle插件是怎么编写的。
新建Gradle插件工程
首先我们先新建一个全新的Android Project,这一层的命名大家随意就好。
新建了Project之后,我们有了一个app Module,此时我们需要新建一个Gradle插件的Library工程
比如我新建了一个叫AsmRock的Library工程
此时它还是一个Android Lib工程,我们需要稍加改造,才能得到一个Gradle插件工程:
创建目录
删除asmrock工程下的src目录中的所有目录
新建目录如下:
- grovvy目录:这个是存放我们gradle插件代码的目录,可以类比成Android中的src/java目录
- resources目录:这个是用来指定我们gradle插件的入口代码的目录,后续我们会增加配置文件,告诉gradle编译器,从哪个类开始执行我们gradle插件的代码
修改build.gradle
- build.gradle原本的声明是,本module为Android Lib工程,我们只需要将整个build.gradle文件全部修改成如下配置就行(全覆盖):
apply plugin: 'groovy'//声明当前module为gradle插件工程
apply plugin: 'maven'//使用maven发布,发布后可以作为依赖使用
repositories {
mavenLocal()
jcenter()
}
dependencies {
implementation gradleApi()
implementation localGroovy()
}
//publish to local directory
def versionName = "1.0.0"
group "com.muxi.asmrock"
version versionName
uploadArchives{ //当前项目可以发布到本地文件夹中
repositories {
mavenDeployer {
repository(url: uri('../repo')) //定义本地maven仓库的地址,方便我们生成的gradle插件代码直接导入进行调试
}
}
}
- 上述操作完成后,我们可以Sync一下整个Studio工程,就可以看到如下情况了
创建Gradle插件demo
- 工程有了,我们照例先来一个Hello Word玩一玩吧
链接Gradle插件的入口代码
- 刚才我们说过了,gradle插件的运行,其实本质上和Java的main()函数一样,也要指定一个入口。Java就是写一个main()函数。
- gradle插件,则需要在resource目录下,新建一个properties文件,指定插件的入口类。
新建properties文件
- 在asmrock下,我们新建过resources目录,一直会延伸到gradle-plugins目录作为尽头,如上面给出过的图片所示。
- 我们需要在gradle-plugins下,新建一个"asm_rock.properties"文件
- asm_rock.properties文件中,编写如下代码:
implementation-class=com.muxi.asmrock.AsmRock
- 上述代码,可以很轻松的看到,我们指明了一个gradle插件的implement类,是xxx对应包名下的某一个类。
properties文件命名:
其实properties文件的命名还有一层含义,比如我们将properties文件命名为“asm_rock.properties”
则当我们将gradle插件发布出去,在代码中使用的时候,我们这样引用:
apply plugin: 'asm_rock'
很熟悉吧?也就是说properties命名成什么,我们的插件引用的名称就是什么,所以,取个好名字吧!
新建入口类:
- 刚才看到,properties文件建立以后,指向了“com.muxi.asmrock.AsmRock”
- 因此我们需要在groovy包下,创建指定的类文件。
- 这里比较有意思:如果你习惯写groovy,就新建对应的包名的groovy文件就好,但是如果groovy玩的不6,那么Java也是支持的。
- 笔者groovy不熟,写起来费劲,所以就是用Java来做演示了。
- 新建AsmRock.java文件:
package com.muxi.asmrock;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class AsmRock implements Plugin<Project> {
@Override
public void apply(Project project) {
System.out.println("Hello world!AsmRock!");
}
}
发布gradle插件
- 代码写好了,接下来将gradle插件发布到本地maven仓库
- 在工程目录下,执行以下gradle命令,发布gradle插件:
./gradlew uploadArchives
- 执行结果如下,就能在工程的repo目录下,看到我们发布的asmrock的gradle插件,包括jar包,maven配置等相关数据
引用gradle插件
- 引用gradle插件就十分简单了,首先我们需要将本地的maven仓库,添加到我们工程的依赖中来:
- 在我们Project的根目录的build.gradle中
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
maven {//配置repo目录为本地Maven仓库地址,这样就能本地引用刚才发布的asmrock插件了
url uri('./repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
classpath 'com.muxi.asmrock:asmrock:1.0.0' //引用asmrock插件
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {//配置repo目录为本地Maven仓库地址,这样就能本地引用刚才发布的asmrock插件了
url uri('./repo')
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- 如上代码,将repo作为maven仓库引入后,通过classpatch接入asmrock插件,Sync工程,我们就完成了gradle插件的引入。
使用gradle插件
- 插件引入后,其实还不会被执行,因为没有工程中的任何module使用了该插件。
- 所以我们需要在具体的某一个module中,去使用该插件
- 此时我们的Project中,有新建的时候默认存在的app Module。我们就让app使用当前新建的gradle插件:
apply plugin: 'com.android.application'
apply plugin: 'asm_rock'
- 如上,在app的build.gradle中,我们apply新增的asm_rock插件,sync一遍代码。
- 此时,完成sync,插件就会在gradle的任务执行过程中,被自动调用执行,在Studio的Build选项中,我们就能看到,gradle插件中的hello world输出
-如上图,到这里,一次完整的gradle插件从创建到被使用的过程就完成了。
小结
- 本篇我们主要介绍了字节码插桩和AOP的一些基本概念,还有怎么去创建一个gradle插件并且完整跑起来。
- 这是做字节码插桩操作最基本的知识点,有了这些作为基础,我们后续再好好讲讲具体怎么玩ASM字节码插桩。
Demo代码
- 上述代码,皆可以在以下地址获取