DRouter、Android Didi ルーティング フレームワークの原理分析

著者: リンバージョン

序文

最近の新しいプロジェクトでは、Clean Architecture+モジュール化+MVVMアーキテクチャを採用しており、ホームページの各タブに対応する機能を別のモジュールにまとめ、相互に依存しないようにしているのですが、その際にモジュール間のページジャンプが発生するという問題が発生しています。 Didi の DRouter が、その優れたパフォーマンス、柔軟なコンポーネント分割、そしてさらに重要な点として、プラグインの増分コンパイル、ルーティング テーブル生成時のマルチスレッド スキャン、実行時のルーティング テーブルの非同期ロード、およびコールバック ActivityResult をサポートしているため、調査対象として選択されました。 .ARouterよりもはるかに優れています。新しいフレームワークを使用すれば十分であるという原則に基づいて、私はフレームワークの原理を理解することに決め、次のような質問を自分自身に定式化しました。

1. フレームワークの設計階層は何ですか?
2. ルーティング テーブルはどのように生成されますか?
3. ルーティング テーブルはどのようにロードされますか?
4.ARouterと比較して、どのようにパフォーマンスを向上させることができますか?

公式ドキュメントを読む

ソース コードを直接読み込むのと比較して、最初に公式ドキュメントを読むのは常に正しいことです。公式が提供した紹介記事は非常によく書かれており、基本的に上記のすべての質問に答えてくれます。

まず、DRouter 紹介のハイライト部分の質問 2、3、4 に対する答えが得られました。

ルーティング テーブルは、コンパイル中にプラグインによって動的に生成されます。プラグインはマルチスレッドを開始し、すべてのコンポーネントを同時に非同期に処理します。インクリメンタル スキャン機能により、開発者は 2 回目のコンパイル時に変更されたコードのみを処理できるため、ルーティング テーブルの生成時間が大幅に短縮されます。

コンパイラで、gradle プラグインと変換を使用して、すべてのクラスをスキャンし、ルーティング テーブルを生成し、増分スキャンをサポートします。質問 2 に答えます。

さらに、フレームワークが初期化されると、メインスレッドの実行をブロックすることなく、サブスレッドが開始されてルーティング テーブルをロードし、可能な限り効率が向上します。

質問3に回答しました。

ルーティング テーブルのロード、ルーティングのインスタンス化、およびサーバーに到達した後のクロスプロセス コマンドの配布では、リフレクション シナリオを使用し、事前に占有されているコードまたは動的に生成されたコードを使用して Java の新規作成と明示的な実行を置き換え、リフレクション実行を回避してパフォーマンスを向上させる必要があります。 。

質問 4 の答えは、リフレクションの使用を減らすことでパフォーマンスが向上するということです。

原理とアーキテクチャの章では、アーキテクチャの設計図が示されています。

全体のアーキテクチャは 3 つの層に分かれており、下からデータ フロー層、コンポーネント層、オープン インターフェイス層となります。

データ フロー層は DRouter の最も重要なコア モジュールであり、プラグインによって生成されたルーティング テーブル、ルーティング要素、動的登録、およびクロスプロセス機能に関連するシリアル化されたデータ フローを保持します。すべてのルーティング フローは、ここから対応するデータを取得し、正しいターゲットに流れます。

RouterPlugin と MetaLoader はルーティング テーブルの生成を担当し、ルーティング要素はスキーム/ホスト/パスなどの情報を格納する RouterMeta を参照します。

コンポーネント層、コアルーティング分散、インターセプタ、ライフサイクル、非同期一時保存とモニタリング、ServiceLoader、多次元フィルタリング、フラグメントルーティング、クロスプロセスコマンドパッケージングなど。

オープン インターフェイス レイヤーは、使用中に接触するいくつかのクラスです。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 メソッドにたどり着きました。インポート パスをよく見てください。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);
    }
}

コンパイル時に変換が追加され、RouterLoaderクラスの生成時にloadメソッドの具体的な実装が追加されたことは推測に難しくありませんが、具体的にはjavaassit API+Gradle Transformとなっているので、その内容を見てみましょう。 drouter-plugin はコンパイル期間中に実行します。

2. コンパイル中に変換する

タイミング図を直接見てください。

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 Reflection は com.didi.drouter.plugin.RouterTransform オブジェクトを作成し、その変換メソッドを実行します。ここで変換ロジックが実際に処理され、その場所は 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ではRouterTaskオブジェクトを新規作成し、そのrunメソッドを実行しますが、その後に出力されるログは通常のコンパイル時に確認できる情報であり、変換に時間がかかっていることがわかります。

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\n" + 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//がコンストラクターに追加され、最後にその生成メソッドが実行されて、ルーティング テーブル、サービス、インターセプターがそれぞれ処理されます。ルーティング テーブルのみを確認しますServiceCollectInterceptorCollect

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) {\n");
        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); \n");
                    //builder.append("    put(\"").append(uri).append("\", ").append(metaBuilder).append(", data); \n");
                } else {
                    items.add("    data.put(\"" + uri + "\", " + metaBuilder + "); \n");
                    //builder.append("    data.put(\"").append(uri).append("\", ").append(metaBuilder).append("); \n");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Collections.sort(items);
            for (String item : items) {
                builder.append(item);
            }
            builder.append("}");

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

ここには多くのロジックがありますが、全体は明確であり、アノテーションと型判定の処理、ルーティング情報の取得、挿入するコードの構築を経て、最後に親クラス AbsRouterCollect のgeneratorClassでloadメソッドの生成を処理します。この時点でコンパイラの仕事は完了です。

ARouter は、コンパイル中にルーティング テーブルも生成する arouter-register プラグインも提供します。違いは、コードを生成するときに、ARouter は ASM を使用し、DRouter は Javassist を使用することです。情報を確認したところ、ASM のパフォーマンスは Javassist より優れていますが、それ以上です。始めるのが難しく、バイトコードの知識を理解する必要があります。Javassist は、複雑なバイトコード レベルの操作に対してより高いレベルの抽象化を提供するため、実装がより簡単かつ高速であり、必要なのはバイトコードの知識が少しだけであり、リフレクション メカニズムを使用します。

3. 実行時にルーティング テーブルをロードします。

ルーティング テーブルをロードするためのロード メソッドを再投稿します。

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 のビルドメソッドを見てください。

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

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 のルーティング部分の原理を分析します。DRouter は、Gradle Transform と Javassist を使用して、コンパイラでルーティング テーブルを生成し、実行時に新しいルーティング クラスを生成し、ルーティング テーブルを非同期に初期化してロードして、高いパフォーマンスを実現します。


Android コンポーネント化のすべての知識ポイントを誰もがよりよく理解できるように、「Android アーキテクチャ学習マニュアル」+「Gradle フレームワークの深い理解」の学習ノートを、主に私自身の学習で作成したいくつかのメモに基づいて以下にまとめました。また、フォローアップのレビューや読書にも便利で、ピットを踏まないようにインターネットで検索する時間を節約できます。必要な場合は、ここをクリックして直接参照​qr21.cn/CaZQLo?BIZ=ECOMMERCE

Androidアーキテクチャ学習マニュアル

Gradle フレームワークについての深い理解

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/maniuT/article/details/131142508