美文网首页
btyeX字节码插桩合并实现原理探究

btyeX字节码插桩合并实现原理探究

作者: 砺雪凝霜 | 来源:发表于2020-04-19 18:19 被阅读0次

    背景

    android app在构建的时候,经常会用到字节码插桩技术,例如无埋点、方法耗时检测、插件化、性能优化检测。它的原理是在将java字节码转成dex文件之前,对项目代码、依赖jar包、android.jar解压缩,并修改其字节码文件,最后再对修改后的字节码文件进行压缩。如果工程较大的话,还有很多字节码插桩的gradle插件的话,那么这个工程是很耗IO的,构建速度会非常慢。

    字节码插桩.png

    gradle插桩会使用到transform,由于公司项目较大,使用的tranform也比较多,通过查看编译过程中task耗时情况,发现tranform占了大量时间。因为每次tranform都会对字节码文件解压,插桩后又进行压缩。如下图所示

    tranform优化前.png

    19年年底开始我就有这样的想法:能不能对tranform进行合并,多个插桩只有一次IO操作,这样不仅可以满足我们的插桩需求,还可以有效减少构建时间。

    tranform优化后.png

    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<Project> {
        @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<IPlugin> plugins = new ArrayList<>();
    
        public void registerPlugin(IPlugin plugin) {
            plugins.add(plugin);
        }
    
        public List<IPlugin> 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<IPlugin> 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<TransformFlow> 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));
        }
    
    

    Bytex tranfrom合并原理流程图

    Bytex tranfrom合并原理流程图.png

    相关文章

      网友评论

          本文标题:btyeX字节码插桩合并实现原理探究

          本文链接:https://www.haomeiwen.com/subject/zexubhtx.html