ARouter的實(shí)現(xiàn)原理淺析

ARouter主要是用于組件化開發(fā)中的組件之間的通信娱仔。

從ARouter的用法透析組件通信原理

// 需要接收消息的組件,要注冊Router注解
@Route(group = "app", path = "/app/Component")
public class ComponentActivity extends AppCompatActivity {

// 通過Router直接發(fā)送事件

// 構(gòu)造參數(shù)
Bundle bundle = new Bundle();
        bundle.putString("data", "data from other module");
        // 發(fā)送事件
        ARouter.getInstance()
                .build("/app/Component") // 跳轉(zhuǎn)頁面路由地址
                .with(bundle) // 傳參
                .navigation();
ARouter的實(shí)現(xiàn)原理

在代碼里加入的@Route注解腥刹,會在編譯時(shí)期通過apt生成一些存儲path和activity.class映射關(guān)系的類文件混移,然后app進(jìn)程啟動的時(shí)候會加載這些類文件桃笙,把保存這些映射關(guān)系的數(shù)據(jù)讀到內(nèi)存里(保存在map里),然后在進(jìn)行路由跳轉(zhuǎn)的時(shí)候腐缤,通過build()方法傳入要到達(dá)頁面的路由地址,ARouter會通過它自己存儲的路由表找到路由地址對應(yīng)的Activity.class(activity.class = map.get(path))肛响,然后new Intent(context, activity.Class)岭粤,當(dāng)調(diào)用ARouter的withString()方法它的內(nèi)部會調(diào)用intent.putExtra(String name, String value),調(diào)用navigation()方法特笋,它的內(nèi)部會調(diào)用startActivity(intent)進(jìn)行跳轉(zhuǎn)剃浇,這樣便可以實(shí)現(xiàn)兩個(gè)相互沒有依賴的module順利的啟動對方的Activity了。

1.ARouter.init(this);的執(zhí)行過程

init()方法源碼如下:

# ARouter.java
/**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

其中調(diào)用了_ARouter的init方法,最終調(diào)用到LogisticsCenter的init()

# _ARouter
protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        // It's not a good idea.
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
        // }
        return true;
    }

#LogisticsCenter
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //billy.qi modified at 2017-12-06
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;

                // 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>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                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);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

在該方法中只做了一件事猎物,導(dǎo)入生成的路由表虎囚。

ARouter.getInstance().build(path)

在build的時(shí)候,傳入要跳轉(zhuǎn)的路由地址蔫磨,build()方法會返回一個(gè)Postcard對象淘讥,我們稱之為跳卡。Postcard里面保存著跳轉(zhuǎn)的信息堤如。

public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;

}

navigation

Postcard 的 navigation() 方法又會調(diào)用到 _ARouter 的以下方法來完成 Activity 的跳轉(zhuǎn)蒲列。

final class _ARouter {

    /**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    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.
            //用于執(zhí)行跳轉(zhuǎn)前的預(yù)處理操作窒朋,可以通過 onPretreatment 方法的返回值決定是否取消跳轉(zhuǎn)
            return null;
        }

        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            //沒有找到匹配的目標(biāo)類
            //下面就執(zhí)行一些提示操作和事件回調(diào)通知

            logger.warning(Consts.TAG, ex.getMessage());
            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }
            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);
                }
            }
            return null;
        }

        if (null != callback) {
            //找到了匹配的目標(biāo)類
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            //沒有開啟綠色通道,那么就還需要執(zhí)行所有攔截器
            //外部可以通過攔截器實(shí)現(xiàn):控制是否允許跳轉(zhuǎn)嫉嘀、更改跳轉(zhuǎn)參數(shù)等邏輯

            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    //攔截器允許跳轉(zhuǎn)
                    _navigation(context, 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 {
            //開啟了綠色通道炼邀,直接跳轉(zhuǎn),不需要遍歷攔截器
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

    //由于本例子的目標(biāo)頁面是 Activity剪侮,所以只看 ACTIVITY 即可
    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;
        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                //Destination 就是指向目標(biāo) Activity 的 class 對象
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                //塞入攜帶的參數(shù)
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

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

                // Navigation in main looper.
                //最終在主線程完成跳轉(zhuǎn)
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            ··· //省略其它類型判斷
        }

        return null;
    }

}
復(fù)制代碼

navigation 方法的重點(diǎn)在于 LogisticsCenter.completion(postcard) 這一句代碼拭宁。它反射調(diào)用 ARouterGroupaccount 的 loadInto 方法,獲取詳細(xì)的路由對應(yīng)信息瓣俯。

completion 方法就是用來獲取詳細(xì)的路由對應(yīng)信息的杰标。該方法會通過 postcard 攜帶的 path 和 group 信息從 Warehouse 取值,如果值不為 null 的話就將信息保存到 postcard 中彩匕,如果值為 null 的話就拋出 NoRouteFoundException腔剂。

    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should complete by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    //為 null 說明目標(biāo)類不存在或者是該 group 還未加載過
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                //groupMeta 為 null,說明 postcard 的 path 對應(yīng)的 group 不存在驼仪,拋出異常
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                  //會執(zhí)行到這里掸犬,說明此 group 還未加載過,那么就來反射加載 group 對應(yīng)的所有 path 信息
                   //獲取后就保存到 Warehouse.routes
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);

                    //移除此 group
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                //重新執(zhí)行一遍
                completion(postcard);   // Reload
            }
        } else {
            //拿到詳細(xì)的路由信息了绪爸,將這些信息存到 postcard 中

            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //省略一些和本例子無關(guān)的代碼
            ···
        }
    }
復(fù)制代碼

回到_ARouter的navigation()方法來湾碎,可以清楚的看到最終人仍然是通過Intent(context,MainActivity.class)的方式來實(shí)現(xiàn)跳轉(zhuǎn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奠货,一起剝皮案震驚了整個(gè)濱河市介褥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌递惋,老刑警劉巖柔滔,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萍虽,居然都是意外死亡睛廊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門杉编,熙熙樓的掌柜王于貴愁眉苦臉地迎上來超全,“玉大人,你說我怎么就攤上這事王财÷延兀” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵绒净,是天一觀的道長见咒。 經(jīng)常有香客問我,道長挂疆,這世上最難降的妖魔是什么改览? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任下翎,我火速辦了婚禮,結(jié)果婚禮上宝当,老公的妹妹穿的比我還像新娘视事。我一直安慰自己,他們只是感情好庆揩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布俐东。 她就那樣靜靜地躺著,像睡著了一般订晌。 火紅的嫁衣襯著肌膚如雪虏辫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天锈拨,我揣著相機(jī)與錄音砌庄,去河邊找鬼。 笑死奕枢,一個(gè)胖子當(dāng)著我的面吹牛娄昆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缝彬,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼萌焰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跌造?” 一聲冷哼從身側(cè)響起杆怕,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤族购,失蹤者是張志新(化名)和其女友劉穎壳贪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寝杖,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡违施,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟幕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磕蒲。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖只盹,靈堂內(nèi)的尸體忽然破棺而出辣往,到底是詐尸還是另有隱情,我是刑警寧澤殖卑,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布站削,位于F島的核電站,受9級特大地震影響孵稽,放射性物質(zhì)發(fā)生泄漏许起。R本人自食惡果不足惜十偶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望园细。 院中可真熱鬧惦积,春花似錦、人聲如沸猛频。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹿寻。三九已至厉亏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烈和,已是汗流浹背爱只。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留招刹,地道東北人恬试。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像疯暑,于是被迫代替她去往敵國和親训柴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 目錄介紹 01.原生跳轉(zhuǎn)實(shí)現(xiàn) 02.實(shí)現(xiàn)組件跳轉(zhuǎn)方式2.1 傳統(tǒng)跳轉(zhuǎn)方式2.2 為何需要路由 03.ARouter...
    楊充211閱讀 2,079評論 0 16
  • What is ARouterA framework for assisting in the renovatio...
    Horps閱讀 659評論 0 1
  • 概述書接上回:ARouter源碼分析上[http://www.reibang.com/p/1e48c588d12...
    Horps閱讀 369評論 0 1
  • 本文由玉剛說寫作平臺提供寫作贊助妇拯,版權(quán)歸玉剛說微信公眾號所有原作者:Xiasem版權(quán)聲明:未經(jīng)玉剛說許可幻馁,不得以任...
    xiasem閱讀 36,302評論 21 146
  • ARouter源碼解讀 以前看優(yōu)秀的開源項(xiàng)目,看到了頁面路由框架ARouter越锈,心想頁面路由是個(gè)啥東東仗嗦,于是乎網(wǎng)上...
    陸元偉閱讀 527評論 0 1