AGP tramsform
Transform 是什么
Transform
是AGP
提供了一个API
,可以在java编译成class后进行一系列的转化,如插桩
和埋点统计等。
我们看下正常编译流程:
启用transform
之后:
示例源码地址:
https://github.com/fanmingyi/AGP-Transfrom-Example
Transform 案例
本文利用Transform
完成如下几件事:
- 将一个
com.example.agptramsform.MainActivity
修改继承自BaseProxyActivity
, - 在onCreate函数前后统计调用时间
为了实现字节码的修改,我们利用javassist
完成。AGP
使用4.1.2
.
首先声明一个插件类,让插件类找到AGP所提供的函数进行Transform
注册.
public abstract class MyGradlePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
//模块应用android插件如plugins { id 'kotlin-android'}后
//会有一个BaseExtension扩展,这个扩展类提供了注册一个Transform的功能
project.getExtensions().findByType(BaseExtension.class)
.registerTransform(new MyTransform(project));
}
}
接下来编写Transform
的实现类即可:
我们需要一个类继承Transform
,然后覆盖一些特定的方法。
public class MyTransform extends Transform {
/*
* 你的Transform的名字,你可以随意取。方便在编译时查看日志
*/
@Override
public String getName() {
return "MyFmyMyTransform";
}
/*
* 这个函数应返回的你的Transform应该处理什么类型的内容
* 你可以声明逆向处理java的资源文件如图片,或者你只处理类
* ContentType的自类有两种:CLASSES和RESOURCES
*
* TransformManager中有一些方面我们使用的预定义集合类型
* class TransformManager{
* Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
* }
*/
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
//我们这里只处理class文件
return CONTENT_CLASS;
}
/**
* 这个函数你想处理的范围.比如说你只想处理当前工程的类和资源(QualifiedContent.Scope.PROJECT).或者你只想处理外部类库的资源(QualifiedContent.Scope.EXTERNAL_LIBRARIES)
**/
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
Set<QualifiedContent.ScopeType> d =
ImmutableSet.of(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.SUB_PROJECTS, QualifiedContent.Scope.EXTERNAL_LIBRARIES);
return d;
}
//当前工程是否支持增量
//如果不开启那么Transform在多次编译下,很耗费时间
@Override
public boolean isIncremental() {
return true;
}
//这个函数会在进行转化的时候进行回调,也就是执行核心的转化方法。
@Override
public void transform(TransformInvocation transformInvocation) {
}
}
这里我们总结这个类的几个方法:
1 getInputTypes
你想处理类还是资源
2 getScopes
你想处理哪里的 类和资源?当前工程还是依赖的类库?
3 isIncremental
当前Transform
是否支持增量调用?如果你不知道什么是增量 返回false
即可
4 transform
执行具体的转化函数
我们看下TransformInvocation
这个类的有关信息
public interface TransformInvocation {
/**
* 返回transform运行的上下文
*/
@NonNull
Context getContext();
/**
* 这个集合是你getScopes和getInputTypes所定义的资源/class的输入信息
*/
@NonNull
Collection<TransformInput> getInputs();
/**
* AGP要求getInputs处理之后的资源或者类输出目录。
* 这里注意getInputs的资源和类哪怕你没处理也要输出到TransformOutputProvider所指定的目录
*/
@Nullable
TransformOutputProvider getOutputProvider();
/**
* 当前执行的是否是增量操作
*/
boolean isIncremental();
}
我们在继续讲解transform
类之前我们先写一个工具类,对某个MainActivity
进行字节操作。
下面我们利用javassist
完成字节操作
public class TransformKit {
ClassPool pool = ClassPool.getDefault();
Project project;
public TransformKit(Project project) {
this.project = project;
}
//searchDir类路径 比如是build/intermediates/javac/debug/classes
public void transform(String searchDir) throws NotFoundException, CannotCompileException, IOException {
//添加到javassist搜索路径中
pool.appendClassPath(searchDir);
//查找类
CtClass mainCtClass = pool.get("com.example.agptramsform.MainActivity");
//我们的将要MainActivity继承的类
CtClass baseProxyCtClass = pool.get("com.example.agptramsform.BaseProxyActivity");
//修改字节码
mainCtClass.setSuperclass(baseProxyCtClass);
//这里再次获得AGP的扩展类,这里主要是为了得到sdk的中android部分类库。不然无法找到android.os.Bundle等android类
BaseExtension android = project.getExtensions().findByType(BaseExtension.class);
//将sdk中的类库资源添加到搜索区域
pool.appendClassPath(android.getBootClasspath().get(0).toString();
pool.importPackage("android.os.Bundle");
CtMethod onCreate = mainCtClass.getDeclaredMethod("onCreate");
//函数运行前的插入一个代码
onCreate.insertBefore("long _startTime = System.currentTimeMillis();");
//函数运行最后一行插入代码
onCreate.insertAfter("long _endTime = System.currentTimeMillis();");
mainCtClass.writeFile(searchDir);
mainCtClass.detach();
baseProxyCtClass.detach();
}
}
我们最后看下MyTransform
的transform
函数实现
public class MyTransform extends Transform {
Project project;
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
//构造一个字节码操作工具类
TransformKit transformKit = new TransformKit(project);
//得到期望的资源/类的输入路径信息
Collection<TransformInput> inputs = transformInvocation.getInputs();
//AGP要求transform输出的目录
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
//当前不是增量更新的那么删除这个transform的缓存文件 //build/intermediates/transforms/xxxxxx
if (!transformInvocation.isIncremental()) {
//如果当前不是增量,应该删除之前的所有缓存信息,防止意料之外错误
outputProvider.deleteAll();
}
//遍历所有输入信息,进行处理
for (TransformInput transformInput : inputs) {
//TransformInput输入类型有两种目录类型,一种是jar类型的,一种就是目录
//遍历jar文件 对jar不操作,但是要输出到out路径
//parallelStream多线程进行处理集合类
transformInput.getJarInputs().parallelStream().forEach(jarInput -> {
//获取AGP要求输出的目录,哪怕你没修改也要输出
File dst = outputProvider.getContentLocation(
jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(),
Format.JAR);
//处理增量情况
if (transformInvocation.isIncremental()) {
switch (jarInput.getStatus()) {
//未做任何改变
case NOTCHANGED:
break;
case ADDED:
//如果一个jar被添加,需要被拷贝回来
case CHANGED:
//是一个新的文件,那么需要拷贝回来
try {
FileUtils.copyFile(jarInput.getFile(), dst);
} catch (IOException e) {
e.printStackTrace();
}
break;
//当前的输入源已经被删除,那么transform下的对应文件理应被删除
case REMOVED:
if (jarInput.getFile().exists()) {
try {
FileUtils.forceDelete(jarInput.getFile());
} catch (IOException e) {
e.printStackTrace();
}
}
break;
}
} else {
//非增量拷贝源集类路径
try {
FileUtils.copyFile(jarInput.getFile(), dst);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
//同上
for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
// 获取输出目录
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);
FileCollection filter = project.fileTree(directoryInput.getFile())
.filter(innerFile -> innerFile.getName().equals("MainActivity.class"));
//当前是增量的状态,所以遍历这个文件夹下的所有文件
if (transformInvocation.isIncremental()) {
Map<File, Status> changedFiles = directoryInput.getChangedFiles();
//遍历文件状态
BiConsumer<File, Status> fileStatusBiConsumer = (file, status) -> {
switch (status) {
//这个文件夹不做任何事情
case NOTCHANGED:
break;
case CHANGED:
case ADDED:
//顺带检查下是否存在我们目标的文件,如果存在那么修改字节码后在拷贝
if (file.getName().equals("MainActivity.class")) {
try {
transformKit.transform(directoryInput.getFile().getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
try {
/**
* 处理方式一 简单粗暴
*/
//偷懒就直接拷贝文件夹 但是效率低
// FileUtils.copyDirectory(directoryInput.getFile(), dest);
/**
* 处理方式二 高效 略复杂
*/
//构建目录连带包名
//file 可能的目录是 /build/intermediates/java/debug/com/fmy/MainActivity.class
//dest 可能目标地址 /build/intermediates/transforms/mytrasnsfrom/debug/40/
//directoryInput.getFile() 可能的输入类的文件夹 /build/intermediates/java/debug/
File dirFile = directoryInput.getFile();
String prefixPath = file.getAbsolutePath().replaceFirst(dirFile.getAbsolutePath(), "");
System.out.println();
//重新拼接成/build/intermediates/transforms/mytrasnsfrom/debug/40/com/fmy/MainActivity.class
File specifyDest = new File(dest.getAbsolutePath(), prefixPath);
FileUtils.copyFile(file, specifyDest);
} catch (Exception e) {
e.printStackTrace();
}
break;
case REMOVED:
//文件被删除直接删除相关文件即可
try {
FileUtils.forceDelete(file);
} catch (IOException e) {
e.printStackTrace();
}
break;
}
};
changedFiles.forEach(fileStatusBiConsumer);
} else {
if (!filter.isEmpty()) {
try {
transformKit.transform(directoryInput.getFile().getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
FileUtils.copyDirectory(directoryInput.getFile(), dest);
}
}
}
}
}
运行后可以看到编译输出多了一个task任务:
参考
Gradle-初探代码注入Transform
Gradle 学习之 Android 插件的 Transform API
Transform Api
还没有评论,来说两句吧...