背景
android app在构建的时候,经常会用到字节码插桩技术,例如无埋点、方法耗时检测、插件化、性能优化检测。它的原理是在将java字节码转成dex文件之前,对项目代码、依赖jar包、android.jar解压缩,并修改其字节码文件,最后再对修改后的字节码文件进行压缩。如果工程较大的话,还有很多字节码插桩的gradle插件的话,那么这个工程是很耗IO的,构建速度会非常慢。
gradle插桩会使用到transform,由于公司项目较大,使用的tranform也比较多,通过查看编译过程中task耗时情况,发现tranform占了大量时间。因为每次tranform都会对字节码文件解压,插桩后又进行压缩。如下图所示
19年年底开始我就有这样的想法:能不能对tranform进行合并,多个插桩只有一次IO操作,这样不仅可以满足我们的插桩需求,还可以有效减少构建时间。
ByteX tranform合并原理探究
2020年年初的时候看到字节跳动团队开源了ByteX地址:https://github.com/bytedance/ByteX,惊奇地发现他们已经实现tranform的合并功能,于是我便花了点时间研究其中原理。
根据byteX文档介绍,必须实现在gradle文件中apply宿主插件,然后再apply 各个子btyex插件,才会进行tranform合并,否则跟普通插桩插件是一样的。
buildscript {
ext.plugin_version="0.1.4"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.bytedance.android.byteX:base-plugin:${plugin_version}"
// Add bytex plugins' dependencies on demand. 按需添加插件依赖
classpath "com.bytedance.android.byteX:refer-check-plugin:${plugin_version}"
// ...
}
}
apply plugin: 'com.android.application'
// apply ByteX宿主
apply plugin: 'bytex'
ByteX {
enable true
enableInDebug false
logLevel "DEBUG"
}
// 按需apply bytex 插件
apply plugin: 'bytex.refer_check'
// ...
1、宿主插件通过ByteXExtension持有各个插件的引用。
那我们就先看base_plugin的代码,如下
public class ByteXPlugin implements Plugin {
@Override
public void apply(@NotNull Project project) {
AppExtension android = project.getExtensions().getByType(AppExtension.class);
ByteXExtension extension = project.getExtensions().create("ByteX", ByteXExtension.class);
android.registerTransform(new ByteXTransform(new Context(project, android, extension)));
}
}
其实就是创建了一个ByteXExtension扩展类,对应宿主插件的gradle配置。然后注册了BtyeXTransform。
ByteXExtension:这个类是实现tranfrom 合并的关键,它会保留每个子BtyeX插件,每个子BtyeX插件都会实现IPlugin接口,并通过执行ByteXExtension的registerPlugin方法完成添加子插件。
byteX子插件的父类AbsPlugin的apply方法中有这样一段代码:
if (!alone()) {
try {
ByteXExtension byteX = project.getExtensions().getByType(ByteXExtension.class);
byteX.registerPlugin(this);
} catch (UnknownDomainObjectException e) {
android.registerTransform(getTransform());
}
} else {
android.registerTransform(getTransform());
}
这里就非常巧妙了,因为宿主插件是首先会apply的的,那么在子ByteX子插件apply方法中是可以拿到宿主插件创建的ByteXExtension对象,那只要调用registerPlugin方法便可以在宿主插件中持有子插件的引用,有了子插件的引用,那么就好办了,只要在宿主中注册的tranfrom中循环执行每个插件的插桩逻辑,就可以完成tranform的合并了。
public class ByteXExtension extends BaseExtension {
private final List plugins = new ArrayList<>();
public void registerPlugin(IPlugin plugin) {
plugins.add(plugin);
}
public List getPlugins() {
return ImmutableList.copyOf(plugins);
}
public void clearPlugins() {
plugins.clear();
}
@Override
public String getName() {
return "byteX";
}
}
2、通过在宿主中注册的tranform,循环迭代每个子插件的tranform插桩逻辑实现tranfrom的合并。
我们来看看ByteXTransform对象,这个类没什么可看到,我们来看看其分类CommonTransform,由于插桩逻辑都是在tranfrom方法中实现的,我们直接看CommonTranfrom的tranform方法:
@Override
public final void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
init(transformInvocation);
TransformContext transformContext = getTransformContext(transformInvocation);
List plugins = getPlugins().stream().filter(p -> p.enable(transformContext)).collect(Collectors.toList());
Timer timer = new Timer();
TransformEngine transformEngine = new TransformEngine(transformContext);
try {
if (!plugins.isEmpty()) {
Queue flowSet = new PriorityQueue<>((o1, o2) -> o2.getPriority() - o1.getPriority());
MainTransformFlow commonFlow = new MainTransformFlow(transformEngine);
// flowSet.add(commonFlow);
for (int i = 0; i < plugins.size(); i++) {
IPlugin plugin = plugins.get(i);
TransformFlow flow = plugin.registerTransformFlow(commonFlow, transformContext);
if (!flowSet.contains(flow)) {
flowSet.add(flow);
}
}
while (!flowSet.isEmpty()) {
TransformFlow flow = flowSet.poll();
if (flow != null) {
if (flowSet.size() == 0) {
flow.asTail();
}
flow.run();
Graph graph = flow.getClassGraph();
if (graph != null) {
//clear the class diagram.we won’t use it anymore
graph.clear();
}
}
}
} else {
transformEngine.skip();
}
afterTransform(transformInvocation);
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e(throwable.getClass().getName(), throwable);
throw throwable;
} finally {
for (IPlugin plugin : plugins) {
try {
plugin.afterExecute();
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e("do afterExecute", throwable);
}
}
transformContext.release();
release();
timer.record("Total cost time = [%s ms]");
if (BooleanProperty.ENABLE_HTML_LOG.value()) {
HtmlReporter.getInstance().createHtmlReporter(getName());
HtmlReporter.getInstance().reset();
}
}
}
这个方法主要实现了什么逻辑:拿到每个子插件的引用,并放到一个队列中,然后依次给每个子插件生成一个TransformFlow插桩流,然后调用其run方法执行,最终通过MainProcessHandler链式调用来实现字节码插桩逻辑。
TransformFlow: 对插桩过程的抽象, 处理全部的构建产物(一般为class文件)的过程定义为一次TransformFlow.一个插件可以独立使用单独的TransformFlow,也可以搭车到全局的MainTransformFlow(traverse,traverseAndroidJar,transform形成一个MainTransformFlow。
TransformEngine:开启线程池去处理插桩逻辑,充分利用打包资源,加快构建速度。
MainProcessHandler:处理插桩逻辑,内部是通过链式调用来实现的。每个btyex子插件都会实现该接口。
@Override
public void traverse(@NotNull String relativePath, @NotNull ClassVisitorChain chain) {
super.traverse(relativePath, chain);
chain.connect(new PreProcessClassVisitor(this.context));
}