ARouter源碼解析

越來越多的項目引入ARouter庫來配合組件化開發(fā),引入ARouter基本上成了項目標(biāo)配音同,那么熟悉ARouter源碼就變得尤為重要了。

ARouter的優(yōu)勢:
  • 支持多模塊使用荧呐,支持組件化開發(fā)
  • 使用注解祠肥,實(shí)現(xiàn)了映射關(guān)系自動注冊與分布式路由管理
  • 編譯期間處理注解,并生成映射文件被去,沒有使用反射主儡,不影響運(yùn)行時性能
  • 映射關(guān)系按組分類、多級管理惨缆,按需初始化
  • 靈活的降級策略糜值,每次跳轉(zhuǎn)都會回調(diào)跳轉(zhuǎn)結(jié)果,避免StartActivity()一旦失敗將會拋出異常
  • 自定義攔截器坯墨,自定義攔截順序臀玄,可以對路由進(jìn)行攔截,比如登錄判斷和埋點(diǎn)處理
  • 支持依賴注入畅蹂,可單獨(dú)作為依賴注入框架使用,從而實(shí)現(xiàn)跨模塊API調(diào)用
  • 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)荣恐,并自動注入?yún)?shù)到目標(biāo)頁面中
  • 支持獲取Fragment

其中液斜,編譯期間處理注解使用了APT技術(shù),引用了JavaPoet技術(shù)來自動生成叠穆。

APT是什么少漆?

APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具硼被,確切的說它是javac的一個工具示损,它用來在編譯時掃描和處理注解。注解處理器以Java代碼(或者編譯過的字節(jié)碼)作為輸入嚷硫,生成.java文件作為輸出检访。
簡單來說就是在編譯期,通過注解生成.java文件仔掸。還有像EventBus脆贵,Butterknife,DataBinding等框架起暮,也是使用了APT卖氨。

JavaPoet是什么?

JavaPoet是squaure公司推出的開源java代碼生成框架,是一個用來生成 .java源文件的Java API筒捺。

ARouter源碼主要組成部分:
  • annotation: 定義路由表的結(jié)構(gòu)柏腻,ARouter路由框架所使用的全部注解,及其相關(guān)類系吭。
  • compiler:創(chuàng)建路由表五嫂,注解編譯處理器,引入“arouter-annotation”村斟,在編譯期把注解標(biāo)注的相關(guān)目標(biāo)類生成映射文件贫导。
  • api: 在運(yùn)行期加載邏輯構(gòu)建路由表,并實(shí)現(xiàn)路由控制蟆盹。

源碼分析

init

從ARouter的初始化出發(fā)開始進(jìn)入源碼:

    fun initARouter() {
        //配置在DEBUG模式下孩灯,打印ARouter的日志
        if (BuildConfig.DEBUG) {
            ARouter.openLog()
            ARouter.openDebug()
        }
        ARouter.init(this)
    }

必須在使用ARouter之前調(diào)用init方法進(jìn)行初始化:

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

變量hasInit用于保證初始化代碼只執(zhí)行一次。真正去實(shí)現(xiàn)初始化是調(diào)用了_ARouter.init方法逾滥,讓_ARouter作實(shí)現(xiàn)類峰档。ARouter作為暴露給用戶調(diào)用的類,真正實(shí)現(xiàn)功能是_ARouter寨昙,將內(nèi)部的功能包裝在了_ARouter讥巡。

_ARouter中init方法代碼:

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

調(diào)用了LogisticsCenter.init方法,傳入線程池executor舔哪。

LogisticsCenter.init方法:

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

        try {
            // These class was generate by arouter-compiler.
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //
            for (String className : classFileNames) {
                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);
                }
            }

            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() + "]");
        }
    }

ClassUtils.getFileNameByPackageName方法做的就是找到app的dex欢顷,然后遍歷出其中的屬于com.alibaba.android.arouter.routes包下的所有類名,打包成集合返回捉蚤。

拿到所有生成類名的集合后抬驴,通過反射實(shí)例化對象并調(diào)用方法,將注解的一些元素添加到static集合中:

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

看各個加載類的接口:

public interface IRouteRoot {
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

public interface IInterceptorGroup {
    void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptor);
}

public interface IProviderGroup {
    void loadInto(Map<String, RouteMeta> providers);
}

public interface IRouteGroup {
    void loadInto(Map<String, RouteMeta> atlas);
}

IRouteRoot的實(shí)現(xiàn)將有@Route注解的module名添加到參數(shù)集合中缆巧,也就是groupsIndex布持。
IInterceptorGroup的實(shí)現(xiàn)將@Interceptor注解的類添加到參數(shù)集合中,也就是interceptorsIndex中陕悬。
IProviderGroup的實(shí)現(xiàn)將繼承自IProvider的類添加到參數(shù)集合中题暖,也就是providersIndex中。

init總結(jié):

init過程就是把所有注解的信息加載內(nèi)存中捉超,并且完成所有攔截器的初始化胧卤。

getInstance()

通過new ARouter()創(chuàng)建或者獲取ARouter單例。

build
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }

然后調(diào)用_ARouter中build方法:

    protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
    }

這里出現(xiàn)一個PathReplaceService拼岳,它是繼承IProvider的接口灌侣,它是預(yù)留給用戶實(shí)現(xiàn)路徑動態(tài)變化功能。extractGroup方法截取路徑中的第一段作為分組名裂问。

build方法會創(chuàng)建并返回一個Postcard(明信片)對象侧啼,將path牛柒,group和bundle傳入Postcard中。

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

Postcard類部分代碼:

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;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim;
    private int exitAnim;
}
navigation

跳轉(zhuǎn)語句最后調(diào)用的是navigation方法痊乾,該方法來自于build返回的Postcard類型的類中皮壁。

    public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
        return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
    }

最終調(diào)用的是_ARouter中的navigation方法:

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
          
         }
    }

completion方法中,使用postcard的path哪审,去path與跳轉(zhuǎn)目標(biāo)的map中獲取routeMeta對象蛾魄,從而獲取其中的destination。

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
Interceptor

攔截功能是通過ARouter提供的interceptorService實(shí)現(xiàn)的湿滓。

public interface InterceptorService extends IProvider {
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}

在_ARouter的navigation跳轉(zhuǎn)之前使用doInterceptions去做攔截處理:

           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) {
                        callback.onInterrupt(postcard);
                    }
                }
            }

最終調(diào)用_navigation方法進(jìn)行跳轉(zhuǎn)或者獲取實(shí)例:

    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

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

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

        return null;
    }

postCard封裝了跳轉(zhuǎn)的destination目標(biāo)滴须,如果postCard的type為activity,則創(chuàng)建intent叽奥,將postCard中的bundle傳入intent的extras扔水,調(diào)用startActivity進(jìn)行跳轉(zhuǎn)。如果type為fragment朝氓,則通過postCard拿到目標(biāo)fragment類魔市,通過反射獲取該Fragment實(shí)例。

參考文章

可能是最詳細(xì)的ARouter源碼分析
ARouter源碼解析

問題:

  • 簡要描述如何自己寫ARouter路由框架

1.先從注解模塊開始赵哲,我們先定義注冊路由地址的注解
2.繼承AbstractProcesser自定義注解處理器解析注解待德。
3.根據(jù)拿到的注解外部類的信息編譯時自動生成代碼,代碼的作用是將注解的外部類的路由信息跟路徑保存在一個map中枫夺。
4.跳轉(zhuǎn)時通過傳入的path路徑根據(jù)map中匹配到的Activity或Fragment進(jìn)行跳轉(zhuǎn)将宪。

  • ARouter將path和組件怎么綁定起來的?存儲在什么容器里橡庞?

在組件上聲明注解加入path较坛,會在ARouter調(diào)用init初始化時,通過注解處理器毙死,在build文件夾中自動生成綁定path和組件的類,ARouter每個組都會生成一個文件喻鳄。

在該組的文件中扼倘,會將該組的各個path,對應(yīng)的Activity.class/Fragment.class進(jìn)行綁定除呵,在map<String, RouteMeta>中存儲起來再菊,用RouteType來區(qū)分是Activity還是fragment。

public class ARouter$$Group$$order implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/order/AuctionBids", RouteMeta.build(RouteType.ACTIVITY, AuctionBidRecordActivity.class, "/order/auctionbids", "iorder", new java.util.HashMap<String, Integer>(){{put("regionCode", 8); put("id", 8); put("status", 8); }}, -1, 1));
    atlas.put("/order/AuctionDetail", RouteMeta.build(RouteType.ACTIVITY, AuctionDetailActivity.class, "/order/auctiondetail", "iorder", new java.util.HashMap<String, Integer>(){{put("regionCode", 8); put("data", 11); put("id", 8); }}, -1, 1));
    atlas.put("/order/Cooperation", RouteMeta.build(RouteType.ACTIVITY, CooperationActivity.class, "/order/cooperation", "iorder", null, -1, 1));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颜曾,一起剝皮案震驚了整個濱河市纠拔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛豪,老刑警劉巖稠诲,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侦鹏,死亡現(xiàn)場離奇詭異,居然都是意外死亡臀叙,警方通過查閱死者的電腦和手機(jī)略水,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝萤,“玉大人渊涝,你說我怎么就攤上這事〈蚕樱” “怎么了跨释?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厌处。 經(jīng)常有香客問我鳖谈,道長,這世上最難降的妖魔是什么嘱蛋? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任蚯姆,我火速辦了婚禮,結(jié)果婚禮上洒敏,老公的妹妹穿的比我還像新娘龄恋。我一直安慰自己,他們只是感情好凶伙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布郭毕。 她就那樣靜靜地躺著,像睡著了一般函荣。 火紅的嫁衣襯著肌膚如雪显押。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天傻挂,我揣著相機(jī)與錄音乘碑,去河邊找鬼。 笑死金拒,一個胖子當(dāng)著我的面吹牛兽肤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绪抛,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼资铡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了幢码?” 一聲冷哼從身側(cè)響起笤休,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎症副,沒想到半個月后店雅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體政基,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年底洗,在試婚紗的時候發(fā)現(xiàn)自己被綠了腋么。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡亥揖,死狀恐怖珊擂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情费变,我是刑警寧澤摧扇,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站挚歧,受9級特大地震影響扛稽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滑负,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一在张、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧矮慕,春花似錦帮匾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痪寻,卻和暖如春螺句,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背橡类。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工蛇尚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顾画。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓取劫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亲雪。 傳聞我的和親對象是個殘疾皇子勇凭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355