• 这是一次给小白的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代码


 评论