Gradle Tansform 的使用

简单介绍

我们都知道 gradle 打包过程由一系列的 task 组成,我们可以通过自定义Gradle Tansform 在编译期间做一些操作。比如可以在编译期间更改生成的 Apk 内容,如资源检查、dex 插桩。

实现步骤

两种方式,一种是要发布到 maven 仓库,这样其他项目也可以使用改插件;另一种本地的一个项目,接下来看看本地的这种实现。
需求:实现项目中每个类前面插入一个字符串属性

  1. 创建一个Groovy模块
  2. 创建一个GatherPlugin
  3. 创建一个GatherTransform
  4. 修改字节码(如:插入字符串)

创建一个名为 buildSrc 的子项目,在这个子项目中编写插件代码,buildSrc 是 Gradle 插件的默认目录,会把这个目录下的插件自动添加到当前项目中去,我们可以在别的子项目中直接使用。

1
2
3
4
5
6
7
8
9
├── buildSrc
│   ├── build.gradle
│   └── src
│   └── main
│   └── groovy
│   └── pub.hanks
│   └── buildsrc
│   ├── WarningPlugin.groovy
│   └── WarningTransform.groovy

build.gradle 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugin: 'groovy'

repositories {
google()
jcenter()
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation gradleApi()
implementation localGroovy()
implementation 'org.javassist:javassist:3.20.0-GA' // 用来修改字节码
implementation 'com.android.tools.build:gradle:3.4.2'
}

然后在 src/main/groovy 目录下创建自定义的 Plugin 并注册 Tansform

1
2
3
4
5
6
7
8
9
10

class WarningPlugin implements Plugin<Project> {

@Override
void apply(Project project) {
def type = project.extensions.getByType(AppExtension.class)
type.registerTransform(new WarningTransform(project))
}

}

看下 Tansform 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package pub.hanks.buildsrc

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import javassist.ClassPool
import javassist.CtClass
import javassist.CtField
import org.apache.commons.codec.digest.DigestUtils
import org.gradle.api.Project

class WarningTransform extends Transform {

final ClassPool sClassPool = ClassPool.getDefault()
Project mProject

WarningTransform(Project mProject) {
this.mProject = mProject
}

/**
* 插入一段代码
*/
void injectToast(String path, Project project) {
// 加入当前路径
sClassPool.appendClassPath(path)
sClassPool.appendClassPath(project.android.bootClasspath[0].toString())

File dir = new File(path)
if (dir.isDirectory()) {
// 遍历文件夹
dir.eachFileRecurse { File file ->
String filePath = file.absolutePath
println("filePath: $filePath")
if (file.name.endsWith(".class")
&& file.name != "R.class"
&& !file.name.startsWith("R\$")
&& filePath.contains("pub/hanks/testgradleplugin/")) {
// 获取Class
CtClass ctClass = sClassPool.makeClass(new FileInputStream(file))

// 解冻
if (ctClass.isFrozen()) {
ctClass.defrost()
}

CtField f = CtField.make("private static final String warning = \"hello hanks\";", ctClass)
ctClass.addField(f)

//写入
ctClass.writeFile(path)

//释放
ctClass.detach()
}
}
}
}

static void copyDirectory(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file, dest)
}

static void copyJar(JarInput jarInput, TransformOutputProvider outputProvider) {
def jarName = jarInput.name
println("jar = " + jarInput.file.getAbsolutePath())
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
// 避免出现 xxx.jar.jar 这样的名字
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0, jarName.length() - 4)
}
def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(jarInput.file, dest)
}

@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
transformInvocation.inputs.each {
input ->
input.directoryInputs.each {
DirectoryInput directoryInput ->
injectToast(directoryInput.file.absolutePath, mProject)
copyDirectory(directoryInput, transformInvocation.outputProvider)
}
// 遍历 jar
input.jarInputs.each {
jarInput ->
copyJar(jarInput, transformInvocation.outputProvider)
}
}
}

@Override
String getName() {
return "warning-plugin"
}

@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}

@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}

@Override
boolean isIncremental() {
return false
}

}


最后在项目中添加插件的引用

1
apply plugin: pub.hanks.buildsrc.WarningPlugin

参考文章

Gradle Transform API :直接修改 class 文件
字节码增强技术探索

文章来自: https://hanks.pub