一、背景及收益
升级sdk或更新依赖库后,可能因为库之间依赖的版本号不同,API有变动时会报:NoSuchMethodError 等错误
二、ByteX实现原理
ByteX是一个基于gradle transform api和ASM的字节码而实现的
三、bytex-referCheck 检测插件的整体实现思路:
- 将所有的子插件注册到宿主插件中,并给每个子插件绑定一个TransformFlow【默认为全局MainTransformFlow】
- 宿主插件的Transform方法中遍历执行每个子插件的TransformFlow执行run方法对.class进行处理:
首先遍历工程中所有的构建产物(.class)和 遍历android.jar里的所有class文件生成类结构图【map:key(className),value(类/接口node)】;
然后再遍历一次工程中所有的构建产物,将每个class文件传给bytex-referCheck子插件的transform方法处理 - 子插件ReferCheckPlugin的transform方法中结合第一次遍历的生成类图,与 ASM ClassVisitor 进行合法性验证
四、名词解释
- traverse过程:遍历一次工程中所有的构建产物(一般来说是class文件),单纯做遍历分析,不对输入文件做修改;
- traverseAndroidJar过程:遍历android.jar里的所有class文件 (哪个版本的android.jar由工程中的target api决定),主要是为了形成完整的类图.
- transform:再遍历一次工程中所有的构建产物,并对class文件做处理后输出(可能是直接回写到本地,也可能作为下一个处理过程的输入)
- 类图Graph:Graph里维护一个map集合 key为类名,value 是类或者接口节点
五、关键过程及代码
5-1:将子插件注册到bytex宿主插件中
首先在子插件的apply()方法中,将子插件注册到bytex宿主插件扩展类ByteXExtension中。
AbsPlugin.java
public final void apply(@NotNull Project project) {
...
ByteXExtension byteX = project.getExtensions().getByType(ByteXExtension.class);
// 向宿主插件 byteX 注册子插件,那么 宿主插件会持有 所有 注册的子插件的引用,通过 ByteXExtension 获取
byteX.registerPlugin(this);
...
在bytex宿主插件中,获取宿主插件扩展ByteXExtension,并将扩展传入ByteXTransform
public class ByteXPlugin implements Plugin<Project> {
@Override
public void apply(@NotNull Project project) {
// 获取 Android 为 App 提供的扩展属性 AppExtension 实例
AppExtension android = project.getExtensions().getByType(AppExtension.class);
// 获取 ByteX 自身创建的扩展属性 ByteXExtension 实例
ByteXExtension extension = project.getExtensions().create("ByteX", ByteXExtension.class);
// 注册 ByteXTransform 实例。ByteXTransform 继承了抽象类 CommonTransform,其实现了关键的 transform 方法
android.registerTransform(new ByteXTransform(new Context(project, android, extension)));
}
}
5-2:为每个子插件注册TransformFlow并执行run方法
从扩展中拿到所有子插件
/**
* 从扩展中获取 所有子插件的引用
* @return
*/
@Override
protected List<IPlugin> getPlugins() {
if (!context.extension.isEnable()) {
return Collections.emptyList();
}
return context.extension.getPlugins();
}
遍历插件注册TransformFlow
CommonTransform.java
private void transformInternal(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
...
plugins.forEach(plugin -> {
manager.bind(new FlowBinder() {
@Override
public TransformFlow bind(TransformFlowerManager manager) {
// 调用子插件的registerTransformFlow 方法 传入全局公共的transform流MainTransformFlow
return plugin.registerTransformFlow(manager.getCommonFlow(), transformContext);
}
});
}
);
...
}
5-3: 公共 MainTransformFlow.runTransform()遍历产物构建类图,以 遍历工程构建产物为例
遍历工程产物
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, new ArrayList<>(handlers))));
getProcessors
@param process:Process.TRAVERSE遍历
@param fileHandler ClassFileAnalyzer类文件解析的
返回的是FileProcessor[]
/**
* 根据传入的状态 返回不同的文件处理器【4 种自定义的】 数组
* @param process 不同的处理状态
* @param fileHandler 根据不同处理状态 传入 不同的文件分析器 【FileHandler 子类: 1 ClassFileAnalyzer 类文件分析器 2 ClassFileTransformer 类文件转换器】
* @return
*/
private FileProcessor[] getProcessors(Process process, FileHandler fileHandler) {
// 在 traverse(遍历) 和 transform(转换) 的过程中,加入自定义的 FileProcessor,提供更大的灵活性
List<FileProcessor> processors = handlers.stream()
.flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) handler -> handler.process(process).stream())
.collect(Collectors.toList());
switch (process) {
// 增量遍历状态
case TRAVERSE_INCREMENTAL:
// FilterFileProcessor:按照指定的条件过滤掉不需要的 FileData,此时的过滤条件是文件的状态不是 Status.NOTCHANGED
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED));
// IncrementalFileProcessor 增量文件处理器,其中包含一个 ClassFileProcessor(FileHandler) 参数,用于对文件解析使用
processors.add(new IncrementalFileProcessor(new ArrayList<>(handlers), ClassFileProcessor.newInstance(fileHandler)));
break;
case TRAVERSE:
case TRAVERSE_ANDROID:
case TRANSFORM:
// ClassFileProcessor 类文件处理器,其中包含一个 ClassFileProcessor(FileHandler) 参数,用于对文件解析使用。
processors.add(ClassFileProcessor.newInstance(fileHandler));
// FilterFileProcessor 按照指定的条件过滤掉不需要的 FileData,此时的过滤条件是文件的状态不是 Status.NOTCHANGED 和 Status.REMOVED
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED && fileData.getStatus() != Status.REMOVED));
break;
default:
throw new RuntimeException("Unknow Process:" + process);
}
return processors.toArray(new FileProcessor[0]);
}
类型为:Process.TRAVERSE 则返回的是FileProcessor[]
index= 0-> FilterFileProcessor
else ->ClassFileProcessor
然后执行的TransformEngine,traverseOnly()方法
/**
* cpu密集型 线程池 执行 遍历 任务
* @param processors
*/
public void traverseOnly(FileProcessor... processors) {
Schedulers.FORKJOINPOOL().invoke(new PerformTraverseTask(context.allFiles(), getProcessorList(processors)));
}
其中 传入的
执行的PerformTraverseTask的compute()方法
@Override
protected void compute() {
List<FileTraverseTask> tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
// 回调所有的 FileTraverseTask 实例的 compute 方法
invokeAll(tasks);
}
执行的是 FileTraverseTask.compute -> TraverseTask.compute-> ProcessorChain 链式任务
ProcessorChain.proceed方法
@Override
public Output proceed(Input input) throws IOException {
if (index >= processors.size()) throw new AssertionError();
// 1 index = 0 时 会从 processors 处理器列表中获取第一个处理器—FilterFileProcessor,并调用它的 process 方法
// 2 index = 1 -> processors.size()-1 之前 时 是 ClassFileProcessor 则会执行 ClassFileProcessor.process()
// 3 index = processors.size()-1 时 为 BackupFileProcessor 则执行 BackupFileProcessor.process()
FileProcessor next = processors.get(index);
return next.process(new ProcessorChain(processors, input, index + 1));
}
遍历任务的话主要在ClassFileProcessor.process() 中
ClassFileProcessor.process
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
FileData fileData = input.getFileData();
if (fileData.getRelativePath().endsWith(".class")) {
// case 1、MainTransformFlow 里执行的是遍历任务 traverseArtifactOnly、traverseAndroidJarOnly的话 传入的 FileHandler 为 ClassFileAnalyzer 那么遍历任务会执行 ClassFileAnalyzer里的 handle方法处理
// case 2、MainTransformFlow 里执行的是transform 任务: 传入的FileHandler 为 ClassFileTransformer 并且 如果 fileData 是.class 文件 ,则调用 ClassFileTransformer 的 handle 方法进行处理。
handler.handle(fileData);
}
// 2、非 类 使用 FileProcessor 列表 最后一个 BackupFileProcessor 的 proceed 则 结束链式调用
return chain.proceed(input);
}
遍历任务看ClassFileAnalyzer的 handle方法
@Override
public void handle(FileData fileData) {
try {
...
...
...
byte[] raw = fileData.getBytes();
String relativePath = fileData.getRelativePath();
// 构建 ClassReader 对象,解析文件
ClassReader cr = new ClassReader(raw);
int flag = getFlag(handlers);
// 创建 ClassVisitorChain 对象,本质是 ClassWriter
ClassVisitorChain chain = getClassVisitorChain(relativePath);
// 如果类图构建器为空,则创建 GenerateGraphClassVisitor 类读取类文件,并构建类图
if (this.mGraphBuilder !=

本文详细阐述了ByteX插件如何通过gradletransformapi和ASM技术,实现对工程构建产物的遍历、类图构建以及 ReferCheckPlugin 的合法验证过程,解决升级SDK后可能出现的 NoSuchMethodError 等问题。
最低0.47元/天 解锁文章
339

被折叠的 条评论
为什么被折叠?



