ARouterの基本的な使い方と原理分析

ARouter の概要

ARouter は、Alibaba によって開発されたオープン ソースのルーティング フレームワークで、Android アプリのコンポーネント変換を支援します。これは、ページとサービスのルーティング機能を提供する Android プラットフォームのミドルウェアです。異なるモジュールのアクティビティ間をジャンプできます。
ARouter の特徴は、柔軟性が高く、プロジェクトの分離に役立つことです。
よく知られているアクティビティ ジャンプに加えて、ARouter はフラグメントの取得、モジュール間の API 呼び出しを可能にするサービスの分離などもサポートしています。詳細については、公式ドキュメントを参照してください:
https://github.com/alibaba/ARouter/blob/マスター/README_CN.md

ARouter 原理の概要

ARouter は @Route アノテーションを使用し、APT テクノロジーを使用してコンパイル中にクラス ファイルを生成し、パスと activityClass の間のマッピング関係を保存します。
アプリ プロセスが開始されると、これらのクラス ファイルが取得され、そこに格納されているマッピング関係データがメモリに読み取られ、ルーティング テーブル マップに保存されます。
ルート ジャンプを実行するときは、ARouter の build() メソッドを通じて到達するページのルーティング アドレスを渡します。ARouter はルーティング テーブル内のルーティング アドレスに対応する activityClass を見つけ、ARouter の withString を通じて new Intent() を渡します。 () メソッド。パラメータを運び、intent.putExtra() を内部で呼び出し、ARouter の Navigation() をジャンプして、startActivity(intent) を内部で呼び出します。
このようにして、異なるモジュールは相互に依存せず、相互のアクティビティを正常に開始できます。

ARouterの基本的な使い方

依存関係と構成を追加する

//注解处理插件
plugins {
    ...
    id 'kotlin-kapt'
}

//注解处理选项
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

//依赖
dependencies {
    // 替换成最新版本, 需要注意的是api要与compiler匹配使用,均使用最新版可以保证兼容
    implementation 'com.alibaba:arouter-api:x.x.x'
    kapt 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

注釈を追加する

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

SDKの初期化

if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

ルーティング操作の開始

// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .withObject("key4", new Test("Jack", "Rose"))
            .navigation();

詳しい使用方法については、公式ドキュメントを参照してください:
https://github.com/alibaba/ARouter/blob/master/README_CN.md

ARouter アーキテクチャの概要

ARouter プロジェクトのコード構造

ARouter プロジェクトのコード構造は上記の通りです。赤枠内が 4 つのコア部分であり、アーキテクチャ上の関係は次のとおりです。

Aルーターのアーキテクチャ

ARouter プロジェクトには、API、コンパイラー、Gradle プラグイン、およびアノテーションの 4 つのモジュールが含まれています。

API

AルーターAPI

API モジュールは、ランチャー、コア、例外、スレッド、facede、ユーティリティ、および基本サブモジュールで構成されます。

  • launcher: ランチャー ARouter が含まれます。
  • core: 物流センター LogsticsCenter や倉庫 Warehouse などのクラスが含まれます。
  • 例外: いくつかの例外クラスが含まれます。
  • Thread: CancellableCountDownLatch が含まれており、ARouter のインターセプタ チェーンは子スレッドで実行されるため、これが使用されます。
  • facede: ナビゲーション コールバック NavigationCallback とインターセプター IInterceptor およびその他のインターフェイスが含まれます。
  • utils: ARouter のカスタム ログ プリンターおよびその他のツール クラスが含まれます。
  • Base: インターセプターの保存に使用される UnitqueKeyTreeMap は 1 つだけです。

コンパイラ

ARouter コンパイラ

Compiler モジュールはルーティング テーブルの生成に使用されます。@Autowired、@Interceptor、および @Route アノテーションに対応するアノテーション プロセッサは、それぞれ AutowiredProcessor、InterceptorProcessor、および RouteProcessor であり、すべて Compiler 内にあります。

プラグインの登録

ARouter 登録プラグイン

Register プラグイン モジュールには、登録コード ジェネレーター RegisterCodeGenerator と RegisterTransform が含まれており、ARouter ルーティング テーブル ロード プラグインが使用されている場合、ルーティング テーブルは Register プラグインによってロードされます。

注釈

ARouter アノテーション

Antaion モジュールは比較的単純で、いくつかの注釈と列挙クラスのみが含まれています。

APTの原則

ARouter は、APT テクノロジーのおかげで非常に使いやすくなっています。APT の機能は、コンパイル段階でコード内の注釈をスキャンして処理し、注釈に基づいて Java ファイルを出力することです。
ARouter は、アノテーション プロセッサの実装を容易にするために 2 つの追加ライブラリも使用します。

  • JavaPoet は、StringBuilder を手動で使用してコードを結合し、IO を使用してファイルを書き込む必要がなく、オブジェクト メソッドを呼び出して必要なコードを生成する方法を提供します。
  • Auto-Service は、APT を登録するための簡単な方法を提供し、本来の面倒な登録手順を回避します。

@ルート

@Route の定義:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();
    ......
}
  • @Target({ElementType.TYPE}): このアノテーションが変更されたクラスであることを示します
  • @Retention(RetentionPolicy.CLASS): コンパイル時まで保持する必要があることを示します

このアノテーションを使用する場合、主要なパラメータ パスが 1 つあります。

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

このようにして、コンパイル時に @Route アノテーションが付与されたクラスを取得し、パスを取得することができます。

ルートプロセッサ

RouteProcessor は、@Route アノテーションに対応するアノテーション プロセッサです。

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor 
  • Auto-Service ライブラリは、プロセッサの自動登録を完了します。
  • @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED}): 現在のプロセッサによってどのアノテーションが処理されるかを示します。

RouteProcessor は BaseProcessor を継承し、init メソッドで各モジュールの moduleName を取得します。

        // Attempt to get user configuration [moduleName]
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(KEY_MODULE_NAME);
            generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
        }

RouteProcessor の process メソッドはアノテーションを処理する場所であり、@Route アノテーションが付けられたすべての要素を直接取得します。

Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);

注釈付き要素を取得した後、 this.parseRoutes(routeElements) メソッドに入ります。このメソッドは JavaPoet を使用して Java ファイルを生成します。このライブラリを使用しない場合は、StringBuilder を使用して Java ファイルのコンテンツを書き込むこともできます。

IRouteGroup

まず RouteProcessor によって生成されたプロダクトを見てみましょう。ARouter によって生成されたプロダクトは以下のパスで確認できます。
画像.png

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity", RouteMeta.build(RouteType.ACTIVITY, YourActivity.class, "/test/activity", "test", null, -1, -2147483648));
  }
}

RouteMeta には、@Route アノテーションが付けられた要素に必要な情報が含まれており、最もわかりやすいのは YourActivity.class で、これを使用すると、Intent を通じてこのアクティビティにジャンプできます。

クラス ARouter$$Group$$test は IRouteGroup から継承し、インターフェイスにloadInto メソッドを実装します。

loadInto メソッドのロジックは非常に単純で、マップを渡し、注釈付きのパス値をキーとして使用し、要素 (RouteMeta) を値としてマップに置きます。このメソッドが完了するとアクティビティの登録が完了します。

IRouteRoot

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}

ARouter$$Root$$app は IRouteRoot インターフェイスを実装しており、内容は非常に似ています。loadInto メソッドを使用して、グループ名を Key として、IRouteGroup 実装クラスを Value として持つコンテンツを Map に挿入します。

デフォルトでは、グループはパスの最初のスラッシュの後の内容です (@Route(path="/group/xxx"))

このメソッドを呼び出すと、グループを通じて IRouteGroup 実装クラスのクラスを取得でき、クラスがインスタンス化された後、上記のメソッドを通じて Activity.class を取得できます。

全体的な構造を次の図に示します。
画像.png

RouteProcessor.process()

戻って RouteProcessor の process メソッドを調べ続けます。

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                this.parseRoutes(routeElements);

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

@Route アノテーションが付けられたすべての要素が取得され、parseRoutes メソッドに入れられて、IRouteGroup と IRouteRoot が生成されます。これは、JavaPoet によって提供されるクラスを使用して、メソッド呼び出しの形式でコードを生成します。

ルーティングテーブルの生成

RouteProcessor の process メソッドは、@Route アノテーションで宣言されたクラスを処理します。大きく分けて、
1. ルーティング要素の取得
2. ルーティングメタ情報の作成
3. ルーティングメタ情報のグループ化
4. ルーティングファイルの生成

上記の分析では、@Route アノテーションが付けられたすべての要素が、roundEnv.getElementsAnnotatedWith() を通じて取得され、parseRoutes メソッドに入力されています。

ここでいうルーティングメタ情報とはRouteMetaのことで、RouteProcessorは@Routeアノテーションで宣言されたActivity、Provider、Service、FragmentをRouteMetaに関連付けます。

for (Element element : routeElements) {
    ......
    // Activity or Fragment
    if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
        // Get all fields annotation by @Autowired
        Map<String, Integer> paramsType = new HashMap<>();
        Map<String, Autowired> injectConfig = new HashMap<>();
        injectParamCollector(element, paramsType, injectConfig);

        if (types.isSubtype(tm, type_Activity)) {
            // Activity
            logger.info(">>> Found activity route: " + tm.toString() + " <<<");
            routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
        } else {
            // Fragment
            logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
            routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
        }

        routeMeta.setInjectConfig(injectConfig);
    } 
    ......
    categories(routeMeta);
}

このコードでは、RouteMeta クラスの構築が完了し、アクティビティが @AutoWired アノテーションを通じて受け取るパラメーターも取得します。次に、category(routeMeta) メソッドを使用してすべての RouteMeta をグループ化します。

なぜグループなのか?プロジェクトが繰り返されるとコンポーネントの数が増加し、マップに大量のコンポーネント情報を入力すると、明らかにメモリに大きな問題が発生し、読み込み時間も増加します。Arouterが採用している方式は「グループ化+オンデマンドローディング」ですが、同時にグループ化することで管理にも便利です。

    private void categories(RouteMeta routeMete) {
        if (routeVerify(routeMete)) {
            logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
            Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
            if (CollectionUtils.isEmpty(routeMetas)) {
                ......
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMete);
            }
        }
        ......
    }

RouteProcessor には groupMap があり、RouteMeta が作成されると、RouteProcessor はグループをキーとしてグループ化し、groupMap に配置します。RouteMeta 自体は Set に入れられ、Set 内のすべての RouteMeta のグループは同じであり、Map の値として機能します。

RouteProcessor が RouteMeta をグループ化した後、JavaPoet を使用してグループ、プロバイダー、およびルートのルーティング ファイルを生成します。ルーティング テーブルはこれらのファイルで構成されます。JavaPoet は Square のオープン ソース コード生成フレームワークです。

            // Write root meta into disk.
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

生成されたルーティング ファイルは、前に示した RouteProcessor の製品であり、次のとおりです。
画像.png

ルーティングテーブルのロード

ルーティング テーブルのロードは、実際には RouteProcessor によって生成されたクラス ファイルをロードすることになります。

ARouter の init() 初期化メソッドを呼び出すと、ARouter は LogisticsCenter の init() メソッドを呼び出します。このメソッドでは、loadRouterMap() が最初にプラグインを通じてルーティング テーブルをロードし、次に現在のルーティング テーブルがロードされているかどうかを判断します。メソッドはプラグインです。そうでない場合は、Dex からルーティング テーブルをロードします。

    /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        ......
        try {
            long startInit = System.currentTimeMillis();
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                //通过插件加载路由表
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                //从Dex中加载路由表
                ......
            }
            ......
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

Dexからルーティングテーブルをロード

Dexからルーティングテーブルをロード

Dex を介してルーティング テーブルをロードするプロセスは、大まかに上の図に示されているとおりです。次に、LogisticsCenter の init() メソッドでの Dex からのルーティング テーブルのロードをステップごとに見てみましょう。

                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

デバッグ モードが設定されている場合、または新しいバージョンの場合は、すべての Dex ファイルをスキャンして com.alibaba.android.arouter.routes で始まるすべてのファイルを検索し、SharePreferences に更新します。それ以外の場合は、解析時間を短縮するために SharePreferences からキャッシュを直接読み取ります。

ここでは ClassUtils を使用して、Dex ファイルを読み取り、Dex ファイルからルーティング テーブルを読み取ります。

                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }

ルーティング テーブルを SharedPreferences に保存すると、クラス名の接尾辞に基づいてクラスが IRouteRoot、IInterceptorGroup、または IProviderGroup のいずれであるかを判断し、それを別のオブジェクトにインスタンス化し、loadInto メソッドを呼び出してクラス ファイルの内容をロードします。インデックス。

プラグイン経由でルーティングテーブルをロードする

ARouter の初期化時間を短縮したい場合は、ARouter の Gradle プラグインを使用すると、ルーティング テーブルを自動的にロードできるため、ARouter は初期化時にクラス情報を読み取る必要がなくなり、初期化時間が短縮されます。時間。
Gradle プラグインを使用してルーティング テーブルの自動ロードを実装する
プラグインの動作原理は、コードがコンパイルされるときにプラグインが LogisticsCenter クラスのloadRouterMap メソッドを見つけて、そのメソッドにルーティング関連のコードを挿入することで、ルーティング テーブルがスキャンされないようにします。初期化中に dex ファイルから。

ルーティングテーブルジャンプ

ARouter.getInstance().build(“/test/activity”).navigation() を使用してルーティング操作とジャンプを開始し、_ARouter の Navigation() メソッドを呼び出します。
_ARouter の Navigation() メソッドには 2 つのオーバーロードがあります。1 つはサービスの作成に使用され、もう 1 つはルーティング ジャンプに使用されます。

    protected <T> T navigation(Class<? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            // Earlier versions did not use the fully qualified name to get the service
            if (null == postcard) {
                // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            if (null == postcard) {
                return null;
            }

            // Set application to postcard.
            postcard.setContext(mContext);

            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }

サービスを作成するサービス プロセスは比較的単純です。LogisticsCenter.buildProvider を通じて、最も基本的なグループとパス情報を含む Postcard ポストカードを作成し、Postcard のコンテキストを設定し、最後に LogisticsCenter.completion を通じてルーティング メタ情報を取得して、それに入力します。ポストカードに。

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        //预处理服务
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            return null;
        }

        // Set context to postcard.
        postcard.setContext(null == context ? mContext : context);

        try {
            //完善Postcard
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
          //完善失败降级策略
          ......
        }
        ......
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            //拦截器链路
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    //按类型跳转
                    _navigation(postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            //按类型跳转
            return _navigation(postcard, requestCode, callback);
        }

        return null;
    }

ルーティング ジャンプ プロセスはもう少し複雑で、大まかに次の手順で構成されます。1. 前処理サービス、2. ポストカードの改善、3. 障害劣化戦略の改善、4. インターセプタ リンク、5. タイプ別のジャンプ。

前処理サービス

前処理サービスを使用すると、ARouter がジャンプする前に PostCard の内容に基づいてジャンプを独立して処理するかどうかを決定でき、そうする場合は onPretreatment() で false を返します。

前処理サービスを実装するには、PretreatmentService インターフェイスを実装し、任意のパス コンテンツを含む @Route アノテーションを追加するだけです。

前処理サービスが実装されており、onPretreatment() が false を返した場合、ジャンプ処理は中断され、処理は続行されません。

ポストカードの改善

前処理サービスを呼び出した後、_ARouter は LogisticsCenter を使用して、RouteProcessor によって生成されたルーティング ファイルであるルーティング テーブルをロードします。

_ARouter が初期化されると LogisticsCenter も初期化され、LogisticsCenter の初期化メソッドでは、RouteProcessor によって作成されたルーティング テーブルが読み込まれ、対応するインデックスに配置されます。

インデックスを使用すると、_ARouter が LogisticsCenter の completed() メソッドを呼び出すときに、インデックスを使用して倉庫のルートからルーティング メタ情報を取得できます。

public synchronized static void completion(Postcard postcard) {
        ......
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {
            ......
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need auto inject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

まず、LogisticsCenter がインデックスに基づいて対応する RouteMeta を見つけられない場合、ルートが埋められていないことを意味します。このとき、LogisticsCenter はグループの RouteMeta を取得し、グループの下のパスをルートに埋めてから、この時点で、ポストカードに記入する情報を取得できます。

RouteMeta を使用すると、routeMeta から値を取得し、それを Postcard 属性に設定し、Uri 内のパラメーターを解析して、Postcard 属性に設定し、その後のジャンプのために Bundle に設定します。

Postcard 情報を入力した後、LogisticsCenter は Postcard のタイプに応じて異なる操作を実行し、タイプが PROVIDER と FRAGMENT の場合は、ポストカードの greenChannel を設定します。これは、実際には Provider と Fragment がインターセプタ リンクをスキップすることを意味します。

障害のダウングレード戦略を改善する

ARouter が Postcard 情報を改善するプロセスで例外に遭遇したとき、ナビゲーション中に NavigationCallback が渡されると、このジャンプの失敗に対して onLost がコールバックされます。コールバックがない場合は、グローバル ダウングレード戦略が使用されます。

            if (null != callback) {
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

前処理サービスと同様に、ダウングレード戦略を実装するには、DegradeService インターフェイスを実装し、任意のパス コンテンツを含む @Route アノテーションを追加するだけです。

インターセプターリンク

greenChannel でない場合はインターセプタリンクに入りますが、それ以外の場合は直接タイプにジャンプします。上記の Postcard を改善する場合、プロバイダーとフラグメントは、インターセプター リンクをスキップする greenChannel を設定します。もちろん、navigation() メソッドの前に greenChannel() メソッドを呼び出して、ジャンプがインターセプタ リンクをスキップできるようにすることもできます。

インターセプタは、ログイン チェックなどのジャンプ中のイベントの処理に使用できます。インターセプタはジャンプとジャンプの間に実行され、複数のインターセプタが優先順位に従って実行されます。

インターセプタを実装するには、IInterceptor インターフェイスを実装し、アノテーション @Interceptor(priority = 8, name = "xxxx") を使用するだけです。優先度はインターセプタの優先順位です。複数のインターセプタは優先順位に従って実行されます。

onContinue() または onInterrupt() メソッドを process メソッドで呼び出す必要があります。これらのメソッドの少なくとも 1 つを呼び出す必要があります。そうしないと、ルーティング プロセスは続行されません。onContinue()は処理が完了してARouterに制御が移ることを示し、onInterrupt()は例外が発生してルーティング処理を中断する必要があることを示します。

種類ごとにジャンプ

インターセプターが処理された後、navigation() 内で _navigation() メソッドが呼び出されます。これは、ジャンプのための特定のメソッドでもあります。この方法ではPostcardのルーティングタイプRouteTypeを元に判定し、タイプに基づいてジャンプします。

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }

                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

RouteType が Activity の場合、アクティビティの開始プロセスは通常のアクティビティの開始プロセスと同じで、Intent を作成し、宛先を渡し、パラメーターのエクストラを渡し、フラグを設定し、アクションを設定し、最後に呼び出します。メインスレッドで startActivity() を使用してターゲット ページを開始します。

RouteType が Provider の場合、つまりカスタム サービスの場合は Postcard の Provider が返され、ARouter のカスタム サービスは依存関係の注入を通じて検出サービスを見つけます。

RouteType が Fragment、Broadcast、ContentProvider の場合は、宛先を介したリフレクションを使用してインスタンスが作成され、Fragment の場合、ARouter はパラメータも設定してインスタンスを返します。

参考記事:
https://juejin.cn/post/6885932290615509000

おすすめ

転載: blog.csdn.net/yuantian_shenhai/article/details/132299670