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

相關(guān)角色:

ARouter:負(fù)責(zé)提供客戶端使用的Api接口闭专,采用了門面模式,實(shí)際上內(nèi)部委托給了_ARouter去處理
_ARouter:路由中心控制器,負(fù)責(zé)控制整個(gè)路由的流程纹烹,通過(guò)Postcard中的信息導(dǎo)航客戶端到目標(biāo)地址(啟動(dòng)某個(gè)Activity或者獲取某個(gè)服務(wù)的實(shí)現(xiàn)等)
LogisticsCenter:后勤中心,負(fù)責(zé)注冊(cè)路由信息到Warehouse和根據(jù)path或者Postcard到數(shù)據(jù)倉(cāng)庫(kù)中獲取數(shù)據(jù)召边,再生成相關(guān)對(duì)象
Warehouse:數(shù)據(jù)倉(cāng)庫(kù)铺呵,負(fù)責(zé)存儲(chǔ)路由配置信息和具體生成的IProvider對(duì)象等。該類基本上都是一些數(shù)據(jù)集合隧熙,沒(méi)有任何邏輯處理
Postcard:明信片片挂,RouteMeta類的子類,用于描述一個(gè)路由的具體信息贞盯,比如音念,目標(biāo)組件類型(Activity||IProvider等)、目標(biāo)組件需要的參數(shù)躏敢,
RouteMeta:路由信息描述類闷愤,存儲(chǔ)目標(biāo)地址的類型,路徑件余,參數(shù)等信息讥脐,LogisticsCenter根據(jù)RouteMeta對(duì)象描述的信息創(chuàng)建明信片遭居。
IRouteGroup:多個(gè)RouteMeta數(shù)據(jù)的容器,類似ViewGroup與View的關(guān)系
IProvider:服務(wù)提供者旬渠,每一個(gè)實(shí)現(xiàn)該接口的類視為一個(gè)獨(dú)立的服務(wù)俱萍,外部客戶端可以根據(jù)path獲取到該服務(wù)。
IInterceptor:攔截器告丢,客戶端可以通過(guò)注冊(cè)IInterceptor的實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)路由的攔截枪蘑,其攔截流程控制是在子線程中按照注冊(cè)順序依次調(diào)用攔截器的process方法將攔截權(quán)釋放給客戶端。其攔截控制器的實(shí)現(xiàn)在InterceptorServiceImpl類中岖免。
PathReplaceService:路徑替換服務(wù)接口岳颇,實(shí)現(xiàn)者需要將path轉(zhuǎn)換為另一個(gè)path

初始化流程:

ARouter框架能將多個(gè)服務(wù)提供者隔離,減少相互之間的依賴颅湘。其實(shí)現(xiàn)的流程和我們平常的快遞物流管理很類似赦役,每一個(gè)具體的快遞包裹就是一個(gè)獨(dú)立的服務(wù)提供者(IProvider),每一個(gè)快遞信息單就是一個(gè)RouteMeta對(duì)象栅炒,客戶端就是快遞的接收方掂摔,而使用@Route注解中的path就是快遞單號(hào)。在初始化流程中赢赊,主要完成的工作就是將所有注冊(cè)的快遞信息表都在物流中心(LogisticsCenter)注冊(cè)乙漓,并將數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)倉(cāng)庫(kù)中(Warehouse)。
初始化的入口是ARouter的init方法释移,其主要是控制初始化的流程叭披,自身不處理具體實(shí)現(xiàn),而是都委托給_ARouter去處理玩讳。

public static void init(Application application) {
        if (!hasInit) {
            //委托給_ARouter去初始化
            hasInit = _ARouter.init(application);
            if (hasInit) {
               //初始化之后調(diào)用afterInit
                _ARouter.afterInit();
            }
        }
    }

_ARouter方法的init方法實(shí)際是調(diào)用LogisticsCenter的init方法涩蜘,下面是其核心代碼:

                Set<String> routerMap;
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    //掃描apk中所有類,找到ROUTE_ROOT_PAKCAGE包下的類熏纯,實(shí)在子線程中完成的
                    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 finish.
                } 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>()));
                }
                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_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);
                    }
                }

LogisticsCenter的init方法主要完成了下面幾件事:

  • 找到com.alibaba.android.arouter.routes包下的所有class文件類名同诫,如果本地緩存的數(shù)據(jù)有效就從本地獲取,如果有更新或者是debug模式樟澜,則通過(guò)掃描安裝包的dex文件獲取
  • 根據(jù)找到的類名去加載相關(guān)的實(shí)例到Warehouse中(類似與快遞信息表入庫(kù))
    實(shí)際上com.alibaba.android.arouter.routes包下的class是由注解解析器自動(dòng)生成的误窖,主要IRouteRoot,IRouteGroup和IProviderGroup的實(shí)現(xiàn)類秩贰,比如當(dāng)我們使用@Route注解某個(gè)類時(shí)霹俺,會(huì)自動(dòng)將這個(gè)類的信息注入的到自動(dòng)生成的上述實(shí)現(xiàn)類中。
    完成初始化之后會(huì)調(diào)用afterInit方法毒费,其主要就是注入攔截控制服務(wù)(InterceptorServiceImpl)
static void afterInit() {
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }
//build方法主要是將path和group封裝到Postcard中丙唧,可以理解成根據(jù)快遞號(hào)生成一個(gè)快遞信息表
protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

而navigation方法是根據(jù)快遞信息表來(lái)生成具體的實(shí)例,這里的攔截服務(wù)控制器實(shí)際是InterceptorServiceImpl對(duì)象觅玻,后面分析具體路由的實(shí)現(xiàn)時(shí)再看其具體的代碼實(shí)現(xiàn)想际。整個(gè)初始化流程到注入InterceptorServiceImpl后就基本完成了

如何實(shí)現(xiàn)路由功能:

這里以一個(gè)具體的使用場(chǎng)景來(lái)看下路由的具體實(shí)現(xiàn)培漏,我們實(shí)現(xiàn)了一個(gè)微博分享的服務(wù),并使用@Route標(biāo)注該服務(wù)沼琉。如下:

@Route(path = "/service/WBShareService")
public class WBShareServiceImp implements IShareService extends IProvider{
      @Override
    public void doShareImage(String text, String title, String path, boolean onlyClient) {
    }
}

然后客戶端需要分享圖片到微博時(shí)的使用代碼如下:

Object obj = ARouter.getInstance().build("/service/WBShareService").navigation();
if (obj instanceof IShareService) {
   ((IShareService)obj).doShareImage("", "", "", false);
}

前面我們分析過(guò)ARouter的build方法了,其就是根據(jù)path生成一個(gè)Postcard對(duì)象桩匪,這里我們接著分析navigation方法打瘪,postcard對(duì)象的navigation方法最終都是委托給_ARouter的navigation方法來(lái)處理。

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        //到物流中心完成postcard的信息填充傻昙,因?yàn)樽畛跎傻膒ostcard對(duì)象只包含path信息闺骚,不包含其他有效信息,比如路由類型妆档,攜帶的參數(shù)等
        LogisticsCenter.completion(postcard);
        //如果不是綠色通道僻爽,則通過(guò)攔截控制器依次調(diào)用不同的攔截器處理信息(類似與一個(gè)包裹在檢查通道了經(jīng)過(guò)多個(gè)掃描檢查)
        if (!postcard.isGreenChannel()) {  
            //每個(gè)攔截器的攔截方法調(diào)用都是在子線程中執(zhí)行的
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        //只要有一個(gè)攔截器攔截該包裹,則回調(diào)onInterrupt方法宣告本次路由被終止
                        callback.onInterrupt(postcard);
                    }
                }
            });
        } else {
            //綠色通道則直接調(diào)用_navigation方法進(jìn)行具體的導(dǎo)航
            return _navigation(context, postcard, requestCode, callback);
        }
        return null;
    }

可以看出navigation方法主要做了如下事情:

  1. 根據(jù)只包含path(理解成只有快遞單號(hào)的快遞信息表)的postcard去物流中心查找具體的路由信息(由編譯時(shí)生成贾惦,在init時(shí)注入)胸梆,完成后續(xù)步驟需要的數(shù)據(jù)填充。
  2. 如果不是綠色通道须板,則將postcard交予攔截控制器碰镜,委托各個(gè)攔截器在子線程執(zhí)行檢查是否攔截。
  3. 如果未攔截习瑰,則執(zhí)行具體的導(dǎo)航功能
    這里先看下LogisticsCenter是怎么去填充信息到postcard中:
public synchronized static void completion(Postcard postcard) {
        //去數(shù)據(jù)倉(cāng)庫(kù)獲取路由信息绪颖,該信息在初始化ARouter時(shí)已經(jīng)注入
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) { 
           //如果沒(méi)有路由信息,則嘗試去數(shù)據(jù)倉(cāng)庫(kù)查找
        } else {
            //找到路由信息后甜奄,則將配置的路由信息填充到Postcard對(duì)象中
            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.
                //這里主要是完成參數(shù)的填充
            }
            //針對(duì)不同的路由類型進(jìn)行處理
            switch (routeMeta.getType()) {
                case PROVIDER:  
                    //如果是服務(wù)提供者柠横,則嘗試獲取其具體實(shí)例,如果沒(méi)有课兄,則根據(jù)路由信息構(gòu)造一個(gè)實(shí)例牍氛,初始化并存儲(chǔ)到數(shù)據(jù)倉(cāng)庫(kù),
                    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;
                        provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                    }
                    postcard.setProvider(instance);
                    //服務(wù)提供者被設(shè)置成綠色渠道烟阐,不用接受攔截檢查
                    postcard.greenChannel();   
                    break;
                case FRAGMENT:
                   //fragment也不用攔截檢查
                    postcard.greenChannel();  
                default:
                    break;
            }
        }
    }

信息填充完之后糜俗,看一下具體的路由實(shí)現(xiàn):

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
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                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);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

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

從上述代碼我們可以看出,不同類型的路由其導(dǎo)航的方式也不一樣

  • 如果是Activity類型曲饱,則將數(shù)據(jù)填充到intent中之后悠抹,調(diào)用ActivityCompat的startActivity或者startActivityForResult方法啟動(dòng)activity。
  • 如果是PROVIDER類型扩淀,則直接返回其服務(wù)提供者
  • 如果是BOARDCAST || CONTENT_PROVIDER || FRAGMENT楔敌,則創(chuàng)建其需要的實(shí)體,并填充數(shù)據(jù)驻谆,再返回該實(shí)體對(duì)象
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卵凑,一起剝皮案震驚了整個(gè)濱河市庆聘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勺卢,老刑警劉巖伙判,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異黑忱,居然都是意外死亡宴抚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門甫煞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)菇曲,“玉大人,你說(shuō)我怎么就攤上這事抚吠〕3保” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵楷力,是天一觀的道長(zhǎng)喊式。 經(jīng)常有香客問(wèn)我,道長(zhǎng)萧朝,這世上最難降的妖魔是什么垃帅? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮剪勿,結(jié)果婚禮上贸诚,老公的妹妹穿的比我還像新娘。我一直安慰自己厕吉,他們只是感情好酱固,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著头朱,像睡著了一般运悲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上项钮,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天班眯,我揣著相機(jī)與錄音,去河邊找鬼烁巫。 笑死署隘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亚隙。 我是一名探鬼主播磁餐,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼阿弃!你這毒婦竟也來(lái)了诊霹?” 一聲冷哼從身側(cè)響起羞延,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脾还,沒(méi)想到半個(gè)月后伴箩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鄙漏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嗤谚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泥张。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呵恢,死狀恐怖鞠值,靈堂內(nèi)的尸體忽然破棺而出媚创,到底是詐尸還是另有隱情,我是刑警寧澤彤恶,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布钞钙,位于F島的核電站,受9級(jí)特大地震影響声离,放射性物質(zhì)發(fā)生泄漏芒炼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一术徊、第九天 我趴在偏房一處隱蔽的房頂上張望本刽。 院中可真熱鬧,春花似錦赠涮、人聲如沸子寓。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)斜友。三九已至,卻和暖如春垃它,著一層夾襖步出監(jiān)牢的瞬間鲜屏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工国拇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洛史,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓酱吝,卻偏偏與公主長(zhǎng)得像虹菲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掉瞳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • ARouter探究(一) 前言 ARouter 是 Alibaba 開(kāi)源的一款 Android 頁(yè)面路由框架毕源,特別...
    Jason騎蝸爬四看世界閱讀 1,320評(píng)論 1 3
  • 前言 隨著項(xiàng)目業(yè)務(wù)邏輯和功能點(diǎn)日益遞增, 邏輯的耦合程度也逐漸升高, 組件化技術(shù)可以很好的解決這個(gè)問(wèn)題, 公司大佬...
    SharryChoo閱讀 1,091評(píng)論 0 9
  • Arouter框架適合項(xiàng)目比較大,模塊多的時(shí)候霎褐,可以實(shí)現(xiàn)解耦址愿,不需要知道跳轉(zhuǎn)的是哪個(gè)activity,只需要知道配...
    破曉11閱讀 3,459評(píng)論 0 2
  • 不知道有沒(méi)有人會(huì)和我一樣冻璃,關(guān)注某一件事情响谓,思想總被文章的作者給左右到,一會(huì)兒覺(jué)得這個(gè)寫的非常有道理省艳,說(shuō)到心眼里去了...
    二求人生閱讀 266評(píng)論 0 0
  • 汽車穿馳在沙棘叢生的小道上娘纷,隨之濺起的石子惡狠狠的敲打著車身。我一陣陣心痛壓抑在內(nèi)心深處跋炕,只能云淡風(fēng)輕的聽(tīng)...
    清泉石下閱讀 378評(píng)論 0 1