Android字节码插桩(一)——自定义Gradle插件:为修改class文件创造条件

666 阅读6分钟

本文代码环境为AGP 7.1.2,Gradle 7.2。

前言

首先要知道,所谓字节码插桩,也就是修改.class文件。而class文件是由java文件编译得到的,我们平时写的业务代码编辑的都是java文件,编写完java代码后,打包的时候才会将java文件编译成class文件,所以修改class文件的代码没办法写在Java文件中。那怎么办呢?这就需要自定义一个Gradle插件,在插件中创建Task,把修改class文件的代码写到Task里。这样在把Task插入到Gradle的Task链中后,在打包编译的过程中就会执行到我们自定义的Task,达到修改class文件的目的。所以使用字节码插桩功能的第一步就是创建Gradle插件。

自定义Gradle插件有三种方式:

1. Build Script方式

2. buildSrc project方式

3. Standardalone project方式

一、Build Script方式

该方式就是在module的build.gradle中直接编写插件类。

优点:操作简单,插件会自动编译并包含在buildScript的classpath中,使用时只需要在build.gradle中直接apply plugin即可,不需要做任何其他配置操作。

缺点:插件在该module之外是不可见的,所以不能在其他模块复用这个插件。

代码示例:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

apply plugin:MyPlugin  // 1

android {
    ...
}

dependencies {
    ...
}

class MyPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {
        println("我是build.gradle方式的自定义插件")
    }
}

如上面所示,直接在app module的build.gradle文件中编写MyPlugin类,实现Plugin<T>接口并重写apply方法,自定义插件就创建完毕了,然后在 1 处使用插件即可。编译后控制台输出:

image.png

二、Standardalone project方式

这里先跳过buildSrc project方式,先看Standardalone project方式。 该方式是创建一个单独的module(或者单独的项目)来编写自定义的插件。

优点:最终可以打包成jar,发布到Maven仓库实现复用,可供其他module或项目引入使用。

缺点:操作较复杂。

创建自定义插件

1、新建module

选择Java or Kotlin Library类型的module

image.png

2、编辑build.gradle文件

  1. 启用maven-public插件,该插件作用是将创建好的自定义插件打包成jar并发布到Maven仓库。
  2. 引入gradleApi库。这个库是开发Gradle插件必备的。比如自定义的插件类MyPlugin所实现的Plugin<T>接口就是这个库中的,如果不引入这个库则找不到Plugin<T>接口。

image.png

3、创建自定义插件类

创建MyPlugin类,实现Plugin<T>接口并重写apply方法。

image.png

注意上图中红框处的导包,Plugin有好几个类,注意不要用错。

4、创建resources资源

创建resources文件夹

image.png 在resources文件夹下创建META-INF目录,在META-INF目录下再创建gradle-plugins目录,注意不要写错。

image.png

在gradle-plugins目录下创建properties文件。名字可以随意取,但必须以.properties结尾(即该文件是配置文件),后面使用插件时会用到(这里取名com.esanwoo.myplugin.properties)。 在文件中输入implementation-class=com.esanwoo.customplugin.MyPlugin代码,等号左边是固定格式,右边就是步骤3中自定义的插件类的完整类名。输入无误的话ctrl+鼠标左键可跳转至该类。

image.png

Tips:java-gradle-plugin:为方便我们自定义插件的插件 emmmmmm...

创建resources资源这个步骤比较繁琐,需要我们自己输入目录和文件名,很可能会一不小心输错。其实官方考虑到了这一点,为方便我们开发自定义插件,已经给我们准备好了插件:java-gradle-plugin。这个插件能简化一些创建插件时的操作。

在自定义插件module的build.gradle中启用java-gradle-plugin插件:

image.png

使用java-gradle-plugin插件后,就可以注释掉java-library插件和gradleApi了,因为java-gradle-plugin插件会自动应用 java-library 插件和gradleApi()。然后再添加如下代码并Sync:

image.png 这样 整个步骤4:创建resources资源 就可以省略了。

上图中gradlePlugin闭包就是java-gradle-plugin插件的一个扩展,闭包中的id设置的就是.properties文件名,那么很容易就能猜到implementationClass设置的就是我们自定义的插件类了。实际上它的作用就是自动帮我们创建resources资源,资源所在目录为build文件夹中,如下图:

image.png

5、发布自定义插件到maven仓库

在build.gradle中编写发布代码,然后Sync,同步完毕后可以在Gradle窗口中看到新增的Task,如下图所示:

image.png

执行该Task即可将插件发布到maven仓库,发布成功后可以在项目根目录看到:

image.png

使用自定义插件

1、引入插件

在项目的build.gradle顶部添加buildScript闭包,在其中添加依赖插件代码,如下图所示。

image.png classpath后面的内容就是发布插件到maven仓库时设置的groupIdartifactIdversion

这时Sync后会发现报错,提示找不到我们自定义的插件。这是由于我们是将插件发布到了我们自定义的本地仓库,所以还需要添加我们本地仓库地址。在settings.gradle文件中加入如下代码即可:

image.png

2、启用插件

插件成功引入后,在需要使用插件的module的build.gradle中启用即可,如下图:

image.png

id后面的值就是上面创建插件步骤4中的com.esanwoo.myplugin.properties文件名,就是在这里用到。

启用插件后编译项目,在控制台会输出:

image.png

三、buildSrc project方式

为什么在Standardalone project方式后面介绍这个方式,因为这个方式其实是一种特殊的Standardalone project方式,它本质上和普通的插件模块是一样的,也是要新建一个module来自定义插件,不过module名必须是buildSrc。buildSrc 模块有如下特点:

  1. buildSrc 模块会被自动识别为参与构建的模块,因此不需要且不能在 settings.gradle 中使用 include 引入,如果引入了会编译出错。
  2. buildSrc 模块会自动被添加到构建脚本的 classpath 中,不需要手动添加。
  3. buildSrc 模块的 build.gradle 执行时机早于其他 Project。
  4. 无法打包成jar发布,故只能在该项目内使用,其他项目中无法使用

下面就用这种方式来创建自定义插件。

创建自定义插件

1、新建module

还是选择Java or Kotlin Library类型,但是名字必须是buildSrc

image.png

发现会编译失败:

image.png

原因就是上面说到的特点中的第一点。在settings.gradle文件中删除掉最后一行的include ':buildSrc'代码即可解决。

2、3步骤同Standardalone project方式,无需4、5步骤

使用自定义插件

无需引入插件,直接在其他module的build.gradle中apply plugin:MyPlugin启用即可。

最后

至此自定义Gradle插件介绍完毕,我们一般大多数都是采用Standardalone project方式来自定义插件,这样可以在其他任何项目中引入使用,只有完全不考虑复用,只为实现本项目的功能而自定义的插件才会采用另外两种方式。