gradle 自定义transform实践

1. 工程配置

1. 添加 gradle api依赖
1
2
compile 'com.android.tools.build:gradle:3.0.1'
compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

2. 自定义transform注入

1
2
3
4
5
6
	class MyPlugin implements Plugin<Project> {
void apply(Project project) {
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new CodeTrans(project))
}
}

3. transform自定义

自定义 transform 扩展自 Transform ,实现 transform 方法即可

3.1 transform的概念

google 的android编译流程,以transform的形式提供给我们可干涉的时机
我们可以在编译成class文件并且生成dex之前拿到这些编译完成的class文件,做一些我们想要的修改。
我们自定义的transform会被包装秤一个 transform task,在 java compile task 之后执行。

3.2 transform的执行流程

transform 执行流程如下

3.3 transform api

1
2
3
4
5
6
7
8
9
public abstract class Transform {
1. public abstract String getName();

2. public abstract Set<ContentType> getInputTypes();

3. public abstract Set<? super Scope> getScopes();

3. public abstract boolean isIncremental(); // 是否支持增量编译
}
1. name : 此 transform的名字,也是这个transform代表的task的名字

2. getInputTypes : 输入类型,作为输入过滤,transformManager中定义了有如下类型。
1
2
3
4
5
6
public static final Set<ContentType> CONTENT_CLASS;
public static final Set<ContentType> CONTENT_JARS;
public static final Set<ContentType> CONTENT_RESOURCES;
public static final Set<ContentType> CONTENT_NATIVE_LIBS;
public static final Set<ContentType> CONTENT_DEX;

3. getScopes : 作用域 transformManager中定义了如下类型
1
2
3
4
5
public static final Set<ScopeType> PROJECT_ONLY;
public static final Set<Scope> SCOPE_FULL_PROJECT;
public static final Set<ScopeType> SCOPE_FULL_WITH_IR_FOR_DEXING;
public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS;

3.4 代码实例

在以下的例子中,我们继承transform方法,修改我们关心的类,在静态函数中插入一行打印语句
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
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {

// super.transform(context, inputs, referencedInputs, outputProvider, isIncremental)

ExtParams params = project.extParams

println " params.logEnable is ${ params.logEnable }"

println "transform method is invoked ====== --------------- "
// System.out.println("=======================================doPathTransform{ context=${context}, inputs=${inputs}, referencedInputs=${referencedInputs}, outputProvider=${outputProvider}, isIncremental=${isIncremental}")


inputs.each { TransformInput input ->

input.directoryInputs.each { DirectoryInput directoryInput ->

//修改我们关注的类
InjectU.inject(directoryInput.file.absolutePath)

//获取输出目录
def dest = outputProvider.getContentLocation(
directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,
Format.DIRECTORY)

// println "directory name location is ${directoryInput.name}"


com.android.utils.FileUtils.copyDirectory(
directoryInput.file,dest)
}

input.jarInputs.each { JarInput jarInput ->

//jar文件一般是第三方依赖库jar文件
// 重命名输出文件(同目录copyFile会冲突)

def jarName = jarInput.name
def md5 = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0,jarName.length() - 4 )
}

//输出
def dest = outputProvider.getContentLocation(jarName,
jarInput.contentTypes,jarInput.scopes,Format.JAR)

// println " jar name location is ${jarInput.name}"

FileUtils.copyFile(jarInput.file,dest)

}
}

}
以下代码用javassist实现了再构造函数中插入语句的代码
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
class InjectU {


static ClassPool pool = ClassPool.getDefault()

static String injectStr = "System.out.println(\"I Love android-----\" ); "


/**
*
* @param path file path
*/
static void inject(String path) {

pool.appendClassPath(path)


def dir = new File(path);

if (dir.isDirectory()) {

dir.eachFileRecurse { File file ->

def filePath = file.absolutePath



//确保当前文件是class文件,并且不是系统自动生成的class文件
if (filePath.endsWith(".class")
&& !filePath.contains('R$')
&& !filePath.contains('R.class')
&& !filePath.contains("BuildConfig.class")) {

println "file path is ${filePath}"

int index = filePath.indexOf("com/example/pluginproject");

if (index != -1) {


int end = filePath.length() - 6 // .class = 6

String className = filePath.substring(index, end)
.replace('\\', '.').replace('/', '.')

println "not -1 class name is ${className}"

def c = pool.getCtClass(className)

if (c.isFrozen()) {
c.defrost()
}


CtConstructor[] declaredConstructors = c.getDeclaredConstructors();
if (declaredConstructors == null || declaredConstructors.length == 0) {
CtConstructor constructor = new CtConstructor(new CtClass[0], c);
c.addConstructor(constructor);
constructor.insertBeforeBody(injectStr);
} else {
declaredConstructors[0].insertBeforeBody(injectStr);
}

c.writeFile(path)
c.detach()
}
}

}
}
}


}

代码已托管至 Github ,点击获取