Gradle Transform是Android官方提供给开发者在项目构建阶段(.class -> .dex转换期间)用来修改.class文件的一套标准API,即把输入的.class文件转变成目标字节码文件。Gradle Transform的使用:1、在build.gradle文件中添加Gradle插件依赖;2、编写Transform类;3、配置Transform。
一、Gradle Transform到底是什么
Gradle Transform是Android官方提供给开发者在项目构建阶段(.class -> .dex转换期间)用来修改.class文件的一套标准API,即把输入的.class文件转变成目标字节码文件,目前比较经典的应用是字节码插桩、代码注入等。
二、Gradle Transform怎么用
1、在build.gradle文件中添加Gradle插件依赖
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:x.x.x'
// 其他插件依赖
}
}
apply plugin: 'com.android.application' // 或其他所需插件
2、编写Transform类
编写Transform类,在其中实现对Java字节码进行的修改。Transform类需要继承自Transform接口并实现其两个方法:
public class MyTransform extends Transform {
@Override
public String getName() {
return "myTransform";
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
// 实现Transform逻辑
}
}
3、配置Transform
在build.gradle文件中配置Transform,将其作为编译期间的一个任务执行:
android {
...
// 配置Transform
transformClassesWithMyTransformForDebug {
// 可选配置项,如下所示
// enable false
// ignoreWarnings true
// enableTransformForJar false
}
}
dependencies {
...
}
完成以上步骤后,Gradle会在编译期间执行Transform对Java字节码进行修改,从而实现各种自动生成代码、字节码增强等功能。
三、Transform编写模板
1、无增量编译
AspectJTransform.groovy代码如下:
class AspectJTransform extends Transform { final String NAME = "JokerwanTransform" @Override String getName() { return NAME } @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 } @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation) // OutputProvider管理输出路径,如果消费型输入为空,你会发现OutputProvider == null TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); transformInvocation.inputs.each { TransformInput input -> input.jarInputs.each { JarInput jarInput -> // 处理Jar processJarInput(jarInput, outputProvider) } input.directoryInputs.each { DirectoryInput directoryInput -> // 处理源码文件 processDirectoryInputs(directoryInput, outputProvider) } } } void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) { File dest = outputProvider.getContentLocation( jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR) // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 FileUtils.copyFiley(jarInput.getFile(), dest) } void processDirectoryInputs(DirectoryInput directoryInput, TransformOutputProvider outputProvider) { File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY) // 建立文件夹 FileUtils.forceMkdir(dest) // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 FileUtils.copyDirectory(directoryInput.getFile(), dest) } }
2、有增量编译
AspectJTransform.groovy代码如下:
class AspectJTransform extends Transform { final String NAME = "JokerWanTransform" @Override String getName() { return NAME } @Override Set<QualifiedContent.ContentType> getInputTypes() { return TransformManager.CONTENT_CLASS } @Override Set<? super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { return true } @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation) boolean isIncremental = transformInvocation.isIncremental() // OutputProvider管理输出路径,如果消费型输入为空,你会发现OutputProvider == null TransformOutputProvider outputProvider = transformInvocation.getOutputProvider() if (!isIncremental) { // 不需要增量编译,先清除全部 outputProvider.deleteAll() } transformInvocation.getInputs().each { TransformInput input -> input.jarInputs.each { JarInput jarInput -> // 处理Jar processJarInputWithIncremental(jarInput, outputProvider, isIncremental) } input.directoryInputs.each { DirectoryInput directoryInput -> // 处理文件 processDirectoryInputWithIncremental(directoryInput, outputProvider, isIncremental) } } } void processJarInputWithIncremental(JarInput jarInput, TransformOutputProvider outputProvider, boolean isIncremental) { File dest = outputProvider.getContentLocation( jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR) if (isIncremental) { // 处理增量编译 processJarInputWhenIncremental(jarInput, dest) } else { // 不处理增量编译 processJarInput(jarInput, dest) } } void processJarInput(JarInput jarInput, File dest) { transformJarInput(jarInput, dest) } void processJarInputWhenIncremental(JarInput jarInput, File dest) { switch (jarInput.status) { case Status.NOTCHANGED: break case Status.ADDED: case Status.CHANGED: // 处理有变化的 transformJarInputWhenIncremental(jarInput.getFile(), dest, jarInput.status) break case Status.REMOVED: // 移除Removed if (dest.exists()) { FileUtils.forceDelete(dest) } break } } void transformJarInputWhenIncremental(JarInput jarInput, File dest, Status status) { if (status == Status.CHANGED) { // Changed的状态需要先删除之前的 if (dest.exists()) { FileUtils.forceDelete(dest) } } // 真正transform的地方 transformJarInput(jarInput, dest) } void transformJarInput(JarInput jarInput, File dest) { // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 FileUtils.copyFile(jarInput.getFile(), dest) } void processDirectoryInputWithIncremental(DirectoryInput directoryInput, TransformOutputProvider outputProvider, boolean isIncremental) { File dest = outputProvider.getContentLocation( directoryInput.getFile().getAbsolutePath(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY) if (isIncremental) { // 处理增量编译 processDirectoryInputWhenIncremental(directoryInput, dest) } else { processDirectoryInput(directoryInput, dest) } } void processDirectoryInputWhenIncremental(DirectoryInput directoryInput, File dest) { FileUtils.forceMkdir(dest) String srcDirPath = directoryInput.getFile().getAbsolutePath() String destDirPath = dest.getAbsolutePath() Map<File, Status> fileStatusMap = directoryInput.getChangedFiles() fileStatusMap.each { Map.Entry<File, Status> entry -> File inputFile = entry.getKey() Status status = entry.getValue() String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath) File destFile = new File(destFilePath) switch (status) { case Status.NOTCHANGED: break case Status.REMOVED: if (destFile.exists()) { FileUtils.forceDelete(destFile) } break case Status.ADDED: case Status.CHANGED: FileUtils.touch(destFile) transformSingleFile(inputFile, destFile, srcDirPath) break } } } void processDirectoryInput(DirectoryInput directoryInput, File dest) { transformDirectoryInput(directoryInput, dest) } void transformDirectoryInput(DirectoryInput directoryInput, File dest) { // to do some transform // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了 FileUtils.copyDirectory(directoryInput.getFile(), dest) } void transformSingleFile(File inputFile, File destFile, String srcDirPath) { FileUtils.copyFile(inputFile, destFile) } }
延伸阅读1:TransformInput
TransformInput是指输入文件的一个抽象,包括:
- DitectoryInput集合:是指以源码的方式参与项目编译的所有目录结构及其目录下的源码文件
- JarInput集合:是指以jar包方式参与项目编译的所有本地jar包和远程jar包(此处的jar包包括aar)