您现在的位置是:首页 >技术教程 >Android滴滴路由框架DRouter原理解析网站首页技术教程

Android滴滴路由框架DRouter原理解析

Android小贾 2024-09-30 12:01:05
简介Android滴滴路由框架DRouter原理解析

作者:linversion

前言

最近的一个新项目使用了Clean Architecture+模块化+MVVM架构,将首页每个tab对应的功能都放到单独的模块且不相互依赖,这时就有了模块间页面跳转的问题,经过一番研究选择了滴滴的DRouter,因为其出色的性能、灵活的组件拆分,更重要的是生成路由表时支持插件增量编译、多线程扫描,运行时异步加载路由表,支持回调式ActivityResult,比ARouter好太多。本着用一个新框架,只会用还不够的原则,我决定去了解一下框架的原理,并给自己制定了以下几个问题:

1、框架的设计分层是什么样的?
2、它是如何生成路由表的?
3、它是如何加载路由表的?
4、相比于ARouter如何提高了性能?

阅读官方文档

相比于直接一头扎进源码,先阅读官方的文档总是没错的,官方给了一篇介绍的文章,写得非常好,基本回答了我以上的所有问题。

首先在介绍DRouter的亮点部分得到了问题2、3、4的答案。

路由表在编译期通过插件动态生成。插件会启动多线程同时异步处理所有的组件;增量扫描功能可以帮助开发者在第二次编译时,只对修改过的代码进行处理,极大地缩短路由表生成的时间。

在编译器使用gradle插件配合transform扫描所有的类,生成路由表,并且支持增量扫描,回答了问题2。

另外框架初始化的时候启动子线程去加载路由表,不阻塞主线程的执行,尽其所能提高效率。

回答了问题3。

加载路由表、实例化路由、以及跨进程命令到达服务端后的分发这些常规应该使用反射的场景,使用预占位或动态生成代码来替换成java的new创建和显式方式执行,最大限度的去避免反射执行,提高性能。

回答了问题4,通过减少使用反射提升了性能。

在原理和架构章节处给了一张架构的设计图:

整体架构分三层,自下而上是数据流层、组件层、开放接口层。

数据流层是DRouter最重要的核心模块,这里承载着插件生成的路由表、路由元素、动态注册、以及跨进程功能相关的序列化数据流。所有的路由流转都会从这里取得对应的数据,进而流向正确的目标。

RouterPlugin和MetaLoader负责生成路由表,路由元素指的是RouterMeta,存放scheme/host/path等信息。

组件层,核心的路由分发、拦截器、生命周期、异步暂存和监控、ServiceLoader、多维过滤、Fragment路由,以及跨进程命令打包等。

开放接口层则是使用时接触到的一些类,API设计得也很简单易用,DRouter类和Request类分别只有75和121行代码。

问题1得到解答,到此处也对整个框架有了一个整体的认识。

阅读源码

1.初始化流程

调用DRouter.init(app)后的时序图如下:

默认是在子线程实现路由表加载,不影响主线程。

    public static void checkAndLoad(final String app, boolean async) {
        if (!loadRecord.contains(app)) {
            // 双重校验锁
            synchronized (RouterStore.class) {
                if (!loadRecord.contains(app)) {
                    loadRecord.add(app);
                    if (!async) {
                        Log.d(RouterLogger.CORE_TAG, "DRouter start load router table sync");
                        load(app);
                    } else {
                        new Thread("drouter-table-thread") {
                            @Override
                            public void run() {
                                Log.d(RouterLogger.CORE_TAG, "DRouter start load router table in drouter-table-thread");
                                load(app);
                            }
                        }.start();
                    }
                }
            }
        }
    }

最终走到了RouterLoader的load方法来加载路由表到一个map中,仔细看它的引入路径是com.didi.drouter.loader.host.RouterLoader,是不存在于源码中的,因为它是编译的时候生成的,位置位于app/build/intermediates/transforms/DRouter/dev/debug/…/com/didi/drouter/loader/host/RouterLoader。

public class RouterLoader extends MetaLoader {
    @Override
    public void load(Map var1) {
        var1.put("@@$$/browse/BrowseActivity", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/browse/BrowseActivity", "com.example.demo.browse.BrowseActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
    }

    public RouterLoader() {
    }
}

public abstract class MetaLoader {

    public abstract void load(Map<?, ?> data);

    // for regex router
    protected void put(String uri, RouterMeta meta, Map<String, Map<String, RouterMeta>> data) {
        Map<String, RouterMeta> map = data.get(RouterStore.REGEX_ROUTER);
        if (map == null) {
            map = new ConcurrentHashMap<>();
            data.put(RouterStore.REGEX_ROUTER, map);
        }
        map.put(uri, meta);
    }

    // for service
    protected void put(Class<?> clz, RouterMeta meta, Map<Class<?>, Set<RouterMeta>> data) {
        Set<RouterMeta> set = data.get(clz);
        if (set == null) {
            set = Collections.newSetFromMap(new ConcurrentHashMap<RouterMeta, Boolean>());
            data.put(clz, set);
        }
        set.add(meta);
    }
}

不难猜出其是在编译期加了一个transform,生成RouterLoader类时加入了load方法的具体实现,具体来说是javaassit API+Gradle Transform,所以去看看drouter-plugin在编译期做了什么。

2.编译期transform

直接看时序图。

创建了一个RouterPlugin,并且注册了一个Gradle Transform。

class RouterPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        ...
        project.android.registerTransform(new TransformProxy(project))
    }
}

class TransformProxy extends Transform {
        @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
        String pluginVersion = ProxyUtil.getPluginVersion(invocation)
        if (pluginVersion != null) {
            ...

            if (pluginJar.exists()) {
                URLClassLoader newLoader = new URLClassLoader([pluginJar.toURI().toURL()] as URL[], getClass().classLoader)
                Class<?> transformClass = newLoader.loadClass("com.didi.drouter.plugin.RouterTransform")
                ClassLoader threadLoader = Thread.currentThread().getContextClassLoader()
                // 1.设置URLClassLoader
                Thread.currentThread().setContextClassLoader(newLoader)
                Constructor constructor = transformClass.getConstructor(Project.class)
                // 2.反射创建一个RouterTransform
                Transform transform = (Transform) constructor.newInstance(project)
                transform.transform(invocation)
                Thread.currentThread().setContextClassLoader(threadLoader)
                return
            } else {
                ProxyUtil.Logger.e("Error: there is no drouter-plugin jar")
            }
        }
    }
}

注释2处反射创建一个com.didi.drouter.plugin.RouterTransform对象,并执行其transform方法,此处真正处理transform逻辑,它的位置位于drouter-plugin模块。

class RouterTransform extends Transform {
    @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
        ...
        // 1.创建一个DRouterTable目录
        File dest = invocation.outputProvider.getContentLocation("DRouterTable", TransformManager.CONTENT_CLASS,
                ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY)
        // 2.执行RouterTask
        (new RouterTask(project, compilePath, cachePathSet, useCache, dest, tmpDir, setting, isWindow)).run()
        FileUtils.writeLines(cacheFile, cachePathSet)
        Logger.v("Link: https://github.com/didi/DRouter")
        Logger.v("DRouterTask done, time used: " + (System.currentTimeMillis() - timeStart) / 1000f  + "s")
    }
}

注释2处new了一个RouterTask对象,并执行其run方法,之后的log输出就是平时编译能看到的信息,表示transform的耗时。

public class RouterTask {
    void run() {
        StoreUtil.clear();
        JarUtils.printVersion(project, compileClassPath);
        pool = new ClassPool();
        // 1.创建ClassClassify
        classClassify = new ClassClassify(pool, setting);
        startExecute();
    }

    private void startExecute() {
        try {
            ...
            // 2.执行ClassClassify的generatorRouter
            classClassify.generatorRouter(routerDir);
            Logger.d("generator router table used: " + (System.currentTimeMillis() - timeStart) + "ms");
            Logger.v("scan class size: " + count.get() + " | router class size: " + cachePathSet.size());
        } catch (Exception e) {
            JarUtils.check(e);
            throw new GradleException("Could not generate d_router table
" + e.getMessage(), e);
        } finally {
            executor.shutdown();
            FileUtils.deleteQuietly(wTmpDir);
        }
    }
}

重点在于ClassClassify这个类,其generatorRouter方法便是最终处理生成路由表的逻辑。

public class ClassClassify {
    private List<AbsRouterCollect> classifies = new ArrayList<>();

    public ClassClassify(ClassPool pool, RouterSetting.Parse setting) {
        classifies.add(new RouterCollect(pool, setting));
        classifies.add(new ServiceCollect(pool, setting));
        classifies.add(new InterceptorCollect(pool, setting));
    }

    public void generatorRouter(File routerDir) throws Exception {
        for (int i = 0; i < classifies.size(); i++) {
            AbsRouterCollect cf = classifies.get(i);
            cf.generate(routerDir);
        }
    }
}

构造函数处添加了RouterCollect/ServiceCollect/InterceptorCollect,最终执行的是他们的generate方法,分别处理路由表、service、拦截器,我们只看路由表的。

class RouterCollect extends AbsRouterCollect {
    @Override
    public void generate(File routerDir) throws Exception {
        // 1.创建RouterLoader类
        CtClass ctClass = pool.makeClass(getPackageName() + ".RouterLoader");
        CtClass superClass = pool.get("com.didi.drouter.store.MetaLoader");
        ctClass.setSuperclass(superClass);

        StringBuilder builder = new StringBuilder();
        builder.append("public void load(java.util.Map data) {
");
        for (CtClass routerCc : routerClass.values()) {
            try {
                // 处理注解、class类型等逻辑
                ...
                StringBuilder metaBuilder = new StringBuilder();
                metaBuilder.append("com.didi.drouter.store.RouterMeta.build(");
                metaBuilder.append(type);
                metaBuilder.append(").assembleRouter(");
                metaBuilder.append(""").append(schemeValue).append(""");
                metaBuilder.append(",");
                metaBuilder.append(""").append(hostValue).append(""");
                metaBuilder.append(",");
                metaBuilder.append(""").append(pathValue).append(""");
                metaBuilder.append(",");
                if ("com.didi.drouter.store.RouterMeta.ACTIVITY".equals(type)) {
                    if (!setting.isUseActivityRouterClass()) {
                        metaBuilder.append(""").append(routerCc.getName()).append(""");
                    } else {
                        metaBuilder.append(routerCc.getName()).append(".class");
                    }
                } else {
                    metaBuilder.append(routerCc.getName()).append(".class");
                }
                metaBuilder.append(", ");
                ...
                metaBuilder.append(proxyCc != null ? "new " + proxyCc.getName() + "()" : "null");
                metaBuilder.append(", ");
                metaBuilder.append(interceptorClass != null ? interceptorClass.toString() : "null");
                metaBuilder.append(", ");
                metaBuilder.append(interceptorName != null ? interceptorName.toString() : "null");
                metaBuilder.append(", ");
                metaBuilder.append(thread);
                metaBuilder.append(", ");
                metaBuilder.append(priority);
                metaBuilder.append(", ");
                metaBuilder.append(hold);
                metaBuilder.append(")");
                ...
                if (isAnyRegex) {
                    // 2. 插入路由表
                    items.add("    put("" + uri + "", " + metaBuilder + ", data); 
");
                    //builder.append("    put("").append(uri).append("", ").append(metaBuilder).append(", data); 
");
                } else {
                    items.add("    data.put("" + uri + "", " + metaBuilder + "); 
");
                    //builder.append("    data.put("").append(uri).append("", ").append(metaBuilder).append("); 
");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Collections.sort(items);
            for (String item : items) {
                builder.append(item);
            }
            builder.append("}");

            Logger.d("
class RouterLoader" + "
" + builder.toString());
            // 3.生成代码
            generatorClass(routerDir, ctClass, builder.toString());
        }
    }
}

此处逻辑比较多,但总体是清晰的,处理完注解和类型的判断,获取路由的信息,构造将要插入的代码,最后统一在父类AbsRouterCollect的generatorClass处理load方法的生成,此时编译器的工作就完成了。

ARouter也提供了arouter-register插件,同是在编译期生成路由表,不同的是在生成代码时,ARouter使用的是ASM,DRouter使用Javassist,查了一下资料,ASM性能比Javassist更好,但更难上手,需要懂字节码知识,Javassist在复杂的字节码级操作上提供了更高级别的抽象层,因此实现起来更容易、更快,只需要懂很少的字节码知识,它使用反射机制。

3.运行期加载路由表

重新贴一下加载路由表的load方法。

public class RouterLoader extends MetaLoader {
    @Override
    public void load(Map var1) {
        var1.put("@@$$/browse/BrowseActivity", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/browse/BrowseActivity", "com.example.demo.browse.BrowseActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
    }

    public RouterLoader() {
    }
}

看下RouteMeta的build方法。

public static RouterMeta build(int routerType) {
    return new RouterMeta(routerType);
}

可见是直接new的一个路由类,这与ARouter直接通过反射创建路由类不同,性能更好。

private static void register(String className) {
    if (!TextUtils.isEmpty(className)) {
        try {
            // 1.反射创建路由类
            Class<?> clazz = Class.forName(className);
            Object obj = clazz.getConstructor().newInstance();
            if (obj instanceof IRouteRoot) {
                registerRouteRoot((IRouteRoot) obj);
            } else if (obj instanceof IProviderGroup) {
                registerProvider((IProviderGroup) obj);
            } else if (obj instanceof IInterceptorGroup) {
                registerInterceptor((IInterceptorGroup) obj);
            } else {
                logger.info(TAG, "register failed, class name: " + className
                        + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
            }
        } catch (Exception e) {
            logger.error(TAG,"register class error:" + className, e);
        }
    }
}

4.总结

本文分析了DRouter路由部分的原理,其在编译器使用Gradle Transform和Javassist生成路由表,运行时new路由类,异步初始化加载路由表,实现了高性能。


为了帮助到大家更好的掌握 Android 组件化中的所有知识点,下面整理了《Android 架构学习手册》+《深入理解Gradle框架》学习笔记,根据自己学习中所做的一些笔录来整的,主要也是方便后续好复习翻阅,省掉在去网上查找的时间,以免在度踩坑,如果大家有需要的可以直接 通过点击此处↓↓↓ 进行参考学习:​qr21.cn/CaZQLo?BIZ=ECOMMERCE

Android 架构学习手册

深入理解Gradle框架

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。