ARouter源碼分析

前置知識

APT

Annotation Processing Tool,自定義注解處理器庵芭。
搞Android的基本上都知道這個吧妹懒。許多第三方庫都使用了APT去實現(xiàn)自己的功能,比如butterknife双吆,比如X2C眨唬,比如我們要講的ARouter。
其基本做法是:

  1. 自定義編譯期注解(比如ARouter源碼中的arouter-annotation模塊)
  2. 自定義AbstractProcessor好乐,實現(xiàn)process方法匾竿,在該方法中掃描步驟1定義的注解,根據(jù)注解信息生成輔助文件(.java文件)(比如ARouter源碼中的arouter-cmpiler模塊)
  3. Runtime時期蔚万,通過反射創(chuàng)建輔助類(獲取步驟2生成的文件的全路徑岭妖,反射),調(diào)用輔助類中的方法(比如ARouter源碼中的arouter-api模塊)
自定義Gradle Plugin

一般是自定義gradle Transform + ASM反璃,實現(xiàn)AOP昵慌,可以在編譯期修改project和第三方依賴庫中的class文件(比如ARouter源碼中的arouter-gradle-plugin模塊),與APT主要是生成.java文件不同淮蜈,ASM操作的是.class文件斋攀。
自定義gradle Transform功能很強大,可以與ASM結(jié)合梧田,修改.class淳蔼,也可以操作資源文件(比如統(tǒng)一壓縮圖片,轉(zhuǎn)png大圖為webp等)裁眯。
至于ASM鹉梨,基于修改.class文件,我們即可以用ASM來插樁統(tǒng)計方法耗時穿稳,也可以用來實現(xiàn)自動化埋點存皂,甚至是修改第三方lib中的crash...

寫在前面

使用方法可以看ARouter
帶著問題看源碼司草,這里主要的問題是:

  1. 初始化都做了什么艰垂?
  2. ARouter是如何實現(xiàn)組件間的路由跳轉(zhuǎn)的?
  3. 攔截器是如何生效的埋虹?
  4. IProvider 的實現(xiàn)機制
  5. ARouter的Gradle Plugin做了哪些優(yōu)化猜憎?

初始化

ARouter.init(getApplication());

ARouter的核心方法。

ARouter#init

    public static void init(Application application) {
    //如果沒初始化過搔课,就執(zhí)行初始化
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        //關(guān)鍵在這里胰柑,初始化路由表
        hasInit = _ARouter.init(application);

        if (hasInit) {
            //加載好路由表以后,執(zhí)行其他操作
            //這里是初始化攔截器
            _ARouter.afterInit();
        }

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

_ARouter#init

protected static synchronized boolean init(Application application) {
    mContext = application;
    //核心方法
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    //創(chuàng)建mainHandler
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}

LogisticsCenter#init

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

    try {
        long startInit = System.currentTimeMillis();
        //load by plugin first
        //這是問題5的關(guān)鍵爬泥,該方法默認空實現(xiàn)(不使用Gradle plugin的時候)
        //暫時跳過該方法柬讨,后面分析
        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.
            //如果是debug模式
            //或者App版本有更新(這里比較的是versionName)
            //重新加載路由表
            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();
                }
                //更新sp中的versionName
                //這里跟上面的PackageUtils.isNewVersion(context)對應(yīng)
                PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
            } else {
                logger.info(TAG, "Load router map from cache.");
                //從sp中取出緩存的路由表(掃描加載路由表是耗時的IO操作,因此使用緩存袍啡,提高加載速度)
                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();
            //遍歷路由表
            //初始化路由表的各個Group
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root.
                    //如果是IRouteRoot的話(文件名以com.alibaba.android.arouter.routes.ARouter$$Root)踩官,反射創(chuàng)建IRouteRoot實例
                    //并執(zhí)行其IRouteRoot#loadInto方法
                    ((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的話(文件名以com.alibaba.android.arouter.routes.ARouter$$Interceptors),反射創(chuàng)建IInterceptorGroup實例
                    //并執(zhí)行其IInterceptorGroup#loadInto方法
                    ((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的話(文件名以com.alibaba.android.arouter.routes.ARouter$$Providers)境输,反射創(chuàng)建IProviderGroup實例
                    //并執(zhí)行其IProviderGroup#loadInto方法
                    ((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() + "]");
    }
}

這個方法算是核心中的核心了蔗牡。
其實也只做了兩件事情

  • 獲取routerMap
  • 遍歷routerMap,反射并執(zhí)行IRouteRoot#loadInto/IInterceptorGroup#loadInto/IProviderGroup#loadInto

ClassUtils#getFileNameByPackageName

public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    //記住嗅剖,這里packageName的值是com.alibaba.android.arouter.routes
    final Set<String> classNames = new HashSet<>();
    //獲取當前Apk目錄下的所有dex文件
    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    //遍歷dex
    for (final String path : paths) {
        //使用線程池(默認核心線程數(shù)為CPU數(shù)+1辩越,最大線程數(shù)CPU+1(也即是只有核心線程),等待隊列ArrayBlockingQueue(容量64))
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;

                try {
                    //加載dex文件
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        dexfile = new DexFile(path);
                    }
                    //找到所有的以com.alibaba.android.arouter.routes開頭的文件
                    //也就是找到包com.alibaba.android.arouter.routes下所有的文件
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }

                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();

    Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
    return classNames;
}

總的來說信粮,只干了一件事情黔攒,掃描所有的dex文件,找到com.alibaba.android.arouter.routes包下的所有文件并返回强缘。這里的操作都是耗時操作督惰。
但是com.alibaba.android.arouter.routes包下都是什么文件呢?
比如:

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

比如:

public class ARouter$$Interceptors$$modulejava implements IInterceptorGroup {
    @Override
    public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
        interceptors.put(7, Test1Interceptor.class);
        interceptors.put(90, TestInterceptor90.class);
    }
}

比如:

public class ARouter$$Providers$$modulejava implements IProviderGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.alibaba.android.arouter.demo.service.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.demo.module1.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
    }
}

比如:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

這些都是APT生成的輔助類欺旧。

這個時候姑丑,我們停下來想一想,到現(xiàn)在為止辞友,ARouter做了哪些事情栅哀?

①項目編譯期,通過APT称龙,生成輔助類(所有的輔助類包名都是com.alibaba.android.arouter.routes)
包括

  • 接口IRouteRoot的實現(xiàn)類(比如ARouter$$Root$$modulejava.java)
  • 接口IProviderGroup的實現(xiàn)類(比如ARouter$$Providers$$modulejava.java)
  • 接口IInterceptorGroup的實現(xiàn)類(比如ARouter$$Interceptors$$modulejava.java)
  • 接口IRouteGroup的實現(xiàn)類(比如ARouter$$Group$$test.java)

②ARouter#init初始化的時候留拾,掃描dex文件,找到①生成的輔助類文件(也即是包com.alibaba.android.arouter.routes下的文件)鲫尊,放到routerMap中

③遍歷routerMap痴柔,找到IRouteRoot/IProviderGroup/IInterceptorGroup的實現(xiàn)類,反射生成實例疫向,并調(diào)用其loadInto方法

注意咳蔚,這里沒有實例化IRouteGroup豪嚎,IRouteGroup的信息都在IRouteRoot中,這樣做的目的是為了實現(xiàn)分組route的加載谈火,用到了哪個group的route的信息侈询,才會加載這個group的信息,沒用到就不加載糯耍。這里可以仔細想想IProviderGroup/IInterceptorGroup/IRouteGroup的區(qū)別扔字。
該方法執(zhí)行完了以后,

  • Warehouse#groupsIndex存放所有的IRouteGroup信息
  • Warehouse#interceptorsIndex存放所有的IProvider信息
  • Warehouse#providersIndex存放所有的IInterceptor信息

至此温技,就完成了初始化路由表的操作革为。
我們回過頭來瞄一眼ARouter#init,里面初始化路由表以后舵鳞,執(zhí)行了_ARouter#afterInit

_ARouter#afterInit

static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

這一句看著很熟悉震檩。

  • 跳轉(zhuǎn)頁面ARouter.getInstance().build("/test/activity").navigation();
  • 獲取其他組件接口HelloService helloService3 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();

頁面路由跳轉(zhuǎn)/IProvider/攔截器都是ARouter.getInstance().build("/test/activity").navigation()這種形式的話,我們就先從攔截器interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();開始分析吧蜓堕。

“/arouter/service/interceptor”

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService{
    //方法省略
}

public interface InterceptorService extends IProvider {

    /**
     * Do interceptions
     */
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}

雖然這里我們是想看攔截器的實現(xiàn)恳蹲,但是要明確一點:InterceptorServiceImpl是IProvider的實現(xiàn)類,獲取InterceptorService也就是獲取一個IProvider俩滥。有一點繞嘉蕾,簡單來說,ARouter使用一個IProvider來實現(xiàn)攔截器的初始化霜旧。
后面的邏輯就變成了獲取一個IProvider上了错忱。

ARouter#build(java.lang.String)

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

_ARouter#build(java.lang.String)

protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //這里PathReplaceService的邏輯先跳過,后面再回頭分析
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        //extractGroup:根據(jù)path獲取group
        //InterceptorService這里獲取到的是"arouter"
        return build(path, extractGroup(path), true);
    }
}

_ARouter#build(java.lang.String, java.lang.String, java.lang.Boolean)

protected Postcard build(String path, String group, Boolean afterReplace) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        if (!afterReplace) {
            //同上挂据,這里PathReplaceService的邏輯先跳過以清,后面再回頭分析
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
        }
        return new Postcard(path, group);
    }
}

也即是ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法中,ARouter.getInstance().build("/arouter/service/interceptor")做的事情就是創(chuàng)建一個Postcard崎逃,其path是"/arouter/service/interceptor",group是"arouter".

Postcard#navigation()

public Object navigation() {
    return navigation(null);
}
public Object navigation(Context context) {
    return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

ARouter#navigation(Context, Postcard, int, NavigationCallback)

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

_ARouter#navigation(Context, Postcard, int, NavigationCallback)

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //預(yù)處理
    //執(zhí)行時機早于攔截器
    //這里掷倔,我們可以加log/攔截路由等
    //默認為空
    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 {
        //關(guān)鍵方法,待分析
        //tips:實際做的事情是:去路由表中查找路由信息个绍,如果是IProvider勒葱,就反射創(chuàng)建實例         
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        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();
                }
            });
        }
        //看NoRouteFoundException也可以猜到,這里是沒找到路由信息(比如path寫錯了巴柿,沒匹配上)
        //如果沒找到路由信息
        //執(zhí)行回調(diào)
        if (null != callback) {
            //如果設(shè)置了callback
            callback.onLost(postcard);
        } else {
            // No callback for this invoke, then we use the global degrade service.
            //如果沒設(shè)置callback凛虽,則執(zhí)行全局的降級策略(暫時記住DegradeService,后面再分析)
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }
        //沒找到路由信息的話广恢,navigation方法就執(zhí)行完了
        return null;
    }
    //找到了路由信息凯旋,執(zhí)行回調(diào)
    if (null != callback) {
        callback.onFound(postcard);
    }
    //判斷是否需要執(zhí)行攔截器邏輯
    //這里只是一個Boolean變量標記值
    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;
}

簡單來說,這里做的事情有

  • LogisticsCenter.completion(postcard):根據(jù)group和path查找路由信息
  • 攔截器攔截
  • _navigation(postcard, requestCode, callback)

LogisticsCenter#completion

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }

    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    //去Warehouse.routes中找,有沒有創(chuàng)建過path對應(yīng)的RouteMeta
    //Warehouse.routes是HashMap至非,key是path钠署,value是RouteMeta
    if (null == routeMeta) {
        //如果Warehouse.routes中沒找到
        //比如第一次加載的時候
        // Maybe its does't exist, or didn't load.
        if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            //如果找不到group信息,則拋出異常
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            //找到了group信息荒椭,則按照group加載
            //比如我們獲取InterceptorService踏幻,這里的group就是"arouter"
            // 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()));
                }
                //加載group "arouter"
                addRouteGroupDynamic(postcard.getGroup(), null);

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

            completion(postcard);   // Reload
        }
    } else {
        //...暫時省略
    }
}
//通過groupName去Warehouse.groupsIndex找對應(yīng)的class
//反射創(chuàng)建class,并執(zhí)行其loadInto方法
public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    if (Warehouse.groupsIndex.containsKey(groupName)){
        // If this group is included, but it has not been loaded
        // load this group first, because dynamic route has high priority.
        Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(groupName);
    }
    //加載一次后就從group中移除
    //從而保證只load一次
    // cover old group.
    if (null != group) {
        group.loadInto(Warehouse.routes);
    }
}

注意addRouteGroupDynamic(postcard.getGroup(), null)這個方法戳杀,通過groupName去groupIndex中查找,那"arouter"對應(yīng)的是誰呢夭苗?正是ARouter$$Group$$arouter.class信卡。
反射創(chuàng)建ARouter$$Group$$arouter對象,并執(zhí)行ARouter$$Group$$arouter#loadInto方法

ARouter$$Root$$arouterapi和ARouter$$Group$$arouter

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

public class ARouter$$Group$$arouter implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    //該方法只做了一件事情
    //把AutowiredServiceImpl.class和InterceptorServiceImpl.class信息加載到Warehouse.routes中
    //Warehouse.routes
    atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", null, -1, -2147483648));
    atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", null, -1, -2147483648));
  }
}

現(xiàn)在我們回過頭來繼續(xù)看LogisticsCenter#completion

public synchronized static void completion(Postcard postcard) {
    //第一次加載题造,Warehouse.routes中找不到""/arouter/service/interceptor""
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        // Maybe its does't exist, or didn't load.
        if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {                
            //那就執(zhí)行ARouter$$Group$$arouter#loadInto
            //執(zhí)行完這個方法后傍菇,Warehouse.routes中就多了兩個元素
            //"/arouter/service/autowired" -> AutowiredServiceImpl.class
            //"/arouter/service/interceptor" -> InterceptorServiceImpl.class
            addRouteGroupDynamic(postcard.getGroup(), null);
            //再執(zhí)行一遍completion           
            completion(postcard);   // Reload
        }
    } else {
        //第二次加載的時候,Warehouse.routes中已經(jīng)有了"/arouter/service/interceptor" -> InterceptorServiceImpl.class
        //設(shè)置參數(shù)
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());                       

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must implement IProvider
                //如果是PROVIDER
                //恰巧我們要找的InterceptorServiceImpl的類型就是PROVIDER
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                //找找Warehouse.providers中有沒有已經(jīng)初始化的實例
                if (null == instance) { // There's no instance of this provider
                    //沒初始化過就執(zhí)行反射界赔,完成初始化
                    //針對InterceptorServiceImpl.class來說丢习,這里就是創(chuàng)建InterceptorServiceImpl實例,然后執(zhí)行InterceptorServiceImpl#init方法
                    //再把初始化好的實例存到Warehouse.providers中
                    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);
                //跳過攔截器攔截淮悼,結(jié)合上面講的ARouter#navigation(Context, Postcard, int, NavigationCallback)
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                //跳過攔截器攔截咐低,結(jié)合上面講的ARouter#navigation(Context, Postcard, int, NavigationCallback)
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

總結(jié)一下LogisticsCenter#completion方法做了啥:

  1. 去Warehouse.groupsIndex找到group對應(yīng)的IRouteGroup,反射創(chuàng)建其實例袜腥,執(zhí)行其IRouteGroup#loadInto方法见擦,這樣,就把group中的path->RouteMeta信息加載到Warehouse.routes中
  2. 從Warehouse.routes中找到path對應(yīng)的RouteMeta信息(包括class類信息)
  3. 如果RouteMeta類型是PROVIDER羹令,則反射創(chuàng)建其實例鲤屡,執(zhí)行其init方法,并把實例保存到Warehouse.providers中

以ARouter.getInstance().build("/arouter/service/interceptor").navigation()舉例說明就是:

  1. Warehouse.groupsIndex找到group為"arouter"的IRouteGroup福侈,這里找到的是ARouter$$Group$$arouter.class
  2. 反射ARouter$$Group$$arouter.class并執(zhí)行其ARouter$$Group$$arouter#loadInto方法
  3. 把"/arouter/service/interceptor"->InterceptorServiceImpl.class信息加載到Warehouse.routes中
  4. 根據(jù)path="/arouter/service/interceptor"從Warehouse.routes中找到InterceptorServiceImpl.class
  5. 反射實例化InterceptorServiceImpl
  6. 執(zhí)行InterceptorServiceImpl#init方法

InterceptorServiceImpl#init

public void init(final Context context) {
    //線程池中執(zhí)行
    LogisticsCenter.executor.execute(new Runnable() {
        @Override
        public void run() {
            if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                //遍歷Warehouse.interceptorsIndex
                for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                    Class<? extends IInterceptor> interceptorClass = entry.getValue();
                    try {
                        反射創(chuàng)建攔截器實例
                        IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                        //執(zhí)行攔截器的初始化方法
                        iInterceptor.init(context);
                        //攔截器實例放到Warehouse.interceptors中
                        Warehouse.interceptors.add(iInterceptor);
                    } catch (Exception ex) {
                        throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                    }
                }

                interceptorHasInit = true;

                logger.info(TAG, "ARouter interceptors init over.");

                synchronized (interceptorInitLock) {
                    interceptorInitLock.notifyAll();
                }
            }
        }
    });
}

init方法做的事情很單一酒来,就是一次性實例化全部的攔截器,存到 Warehouse.interceptors中肪凛。(想想為什么要這么做堰汉?)
這樣,ARouter.getInstance().build("/arouter/service/interceptor").navigation()就分析完了伟墙,ARouter#init的時候,會創(chuàng)建所有的攔截器實例衡奥。ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法返回的是InterceptorServiceImpl的實例。

另外远荠,_ARouter#navigation(Context, Postcard, int, NavigationCallback)方法的最后矮固,調(diào)用了_ARouter#_navigation

_ARouter#_navigation

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

    switch (postcard.getType()) {
        case ACTIVITY:
            //如果是ACTIVITY(頁面跳轉(zhuǎn))
            //就構(gòu)建Intent
            // 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:
            //反射創(chuàng)建實例
            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;
}

方法看著長,內(nèi)容卻很簡單:

  • 如果是頁面跳轉(zhuǎn),就構(gòu)建Intent档址,調(diào)系統(tǒng)的ActivityCompat.startActivityForResult方法
  • 如果是PROVIDER盹兢,返回上一步LogisticsCenter.completion中已經(jīng)創(chuàng)建好的實例
  • 如果是BOARDCAST/CONTENT_PROVIDER/FRAGMENT,反射創(chuàng)建實例

這樣守伸,ARouter#init就分析完了绎秒,總結(jié)一下:

  1. 掃描所有的dex文件,找到包com.alibaba.android.arouter.routes下的全部文件(耗時操作)
  2. 如果是IRouteRoot/IInterceptorGroup/IProviderGroup尼摹,就反射創(chuàng)建實例见芹,執(zhí)行其loadInto方法,這樣蠢涝,以group為單位的路由表信息就被存放到Warehouse.groupsIndex/Warehouse.interceptorsIndex/Warehouse.providersIndex中
  3. 初始化攔截器(創(chuàng)建所有的攔截器實例玄呛,存到Warehouse.interceptors中)

另外使用ARouter.getInstance().build("path").navigation()方法獲取IProvider的流程如下:

  1. 如果Warehouse.routes中已經(jīng)有path對應(yīng)的RouteMeta,則執(zhí)行步驟3和二,如果沒有(第一次初始化)徘铝,則執(zhí)行步驟2
  2. Warehouse.groupsIndex中查找group對應(yīng)的類,實例化并執(zhí)行l(wèi)oadInto方法惯吕,將RouteMeta信息加載到Warehouse.routes惕它,然后重新執(zhí)行步驟1
  3. 取出Warehouse.routes中path對應(yīng)的RouteMeta,通過反射實例化class對象废登,并執(zhí)行其init方法淹魄,實例存到Warehouse.providers中,并返回該實例對象

Activity跳轉(zhuǎn)的流程如下:

  1. 同上
  2. 同上
  3. 取出Warehouse.routes中path對應(yīng)的RouteMeta堡距,創(chuàng)建Intent對象揭北,調(diào)用ActivityCompat.startActivityForResult實現(xiàn)頁面跳轉(zhuǎn)

至此,我們回答了問題1/問題2和問題4.
下面我們來看下剩下的問題
問題3:攔截器是如何生效的吏颖?
我們可以看看_ARouter

private static InterceptorService interceptorService;
static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //省略...
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(postcard, requestCode, callback);
            }
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    }
    //省略...
}

執(zhí)行_ARouter#navigation的時候搔体,執(zhí)行了interceptorService.doInterceptions方法,前面我們已經(jīng)知道半醉,執(zhí)行了interceptorService實際上是InterceptorServiceImpl疚俱。

InterceptorServiceImpl#doInterceptions

public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
        //檢查攔截器是否已全部初始化
        //如果沒完全初始化,該方法會阻塞
        checkInterceptorsInitStatus();

        if (!interceptorHasInit) {
            callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
            return;
        }
        
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                try {
                    _execute(0, interceptorCounter, postcard);
                    //等待攔截器全部執(zhí)行完缩多,超時時間(默認300s)
                    interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                    if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                        //如果還有列表Warehouse.interceptors中攔截器沒執(zhí)行完
                        //報超時
                        callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                    } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                        //被某一攔截器攔截
                        callback.onInterrupt((Throwable) postcard.getTag());
                    } else {
                        //不攔截
                        callback.onContinue(postcard);
                    }
                } catch (Exception e) {
                    callback.onInterrupt(e);
                }
            }
        });
    } else {
        callback.onContinue(postcard);
    }
}

InterceptorServiceImpl#_execute

private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
    if (index < Warehouse.interceptors.size()) {
        IInterceptor iInterceptor = Warehouse.interceptors.get(index);
        iInterceptor.process(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                // Last interceptor excute over with no exception.
                counter.countDown();
                _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
            }

            @Override
            public void onInterrupt(Throwable exception) {
                // Last interceptor execute over with fatal exception.

                postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                counter.cancel();
                // Be attention, maybe the thread in callback has been changed,
                // then the catch block(L207) will be invalid.
                // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
//                    if (!Looper.getMainLooper().equals(Looper.myLooper())) {    // You shouldn't throw the exception if the thread is main thread.
//                        throw new HandlerException(exception.getMessage());
//                    }
            }
        });
    }
}

這里一個一個調(diào)用攔截器呆奕,如果有攔截器攔截,就中斷調(diào)用衬吆,否則梁钾,調(diào)用下一個攔截器進行攔截。

所以逊抡,攔截器總結(jié)如下

  1. ARouter#init時姆泻,反射創(chuàng)建全部攔截器實例零酪,放到Warehouse.interceptors中
  2. Postcard#navigation()時,遍歷Warehouse.interceptors調(diào)用各個攔截器攔截

最后拇勃,我們來看下最后一個問題四苇。
問題5 ARouter的Gradle Plugin做了哪些優(yōu)化?
該問題的關(guān)鍵是LogisticsCenter#loadRouterMap

LogisticsCenter#loadRouterMap

private static void loadRouterMap() {
    registerByPlugin = false;
    // auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    // registerRouteRoot(new ARouter..Root..modulejava());
    // registerRouteRoot(new ARouter..Root..modulekotlin());
}

private static void loadRouterMap() {
    registerByPlugin = false;
    // auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
    register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
}

上面的是gradle plugin修改之前的方咆,下面的loadRouterMap是gradle plugin修改之后的月腋。

private static void register(String className) {
    if (!TextUtils.isEmpty(className)) {
        try {
            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);
        }
    }
}
private static void registerRouteRoot(IRouteRoot routeRoot) {
    markRegisteredByPlugin();
    if (routeRoot != null) {
        routeRoot.loadInto(Warehouse.groupsIndex);
    }
}
private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
    markRegisteredByPlugin();
    if (interceptorGroup != null) {
        interceptorGroup.loadInto(Warehouse.interceptorsIndex);
    }
}
private static void registerProvider(IProviderGroup providerGroup) {
    markRegisteredByPlugin();
    if (providerGroup != null) {
        providerGroup.loadInto(Warehouse.providersIndex);
    }
}
private static void markRegisteredByPlugin() {
    if (!registerByPlugin) {
        registerByPlugin = true;
    }
}

瞄一眼register方法我們就能明白,這還是之前的那一套瓣赂,跟不使用gradle plugin不同的地方在于榆骚,這里不需要掃描dex去找IRouteRoot/IInterceptorGroup/IProviderGroup,在編譯期煌集,gradle plugin就已經(jīng)找到了這些妓肢,然后生成新的loadRouterMap方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牙勘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子所禀,更是在濱河造成了極大的恐慌方面,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件色徘,死亡現(xiàn)場離奇詭異恭金,居然都是意外死亡,警方通過查閱死者的電腦和手機褂策,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門横腿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斤寂,你說我怎么就攤上這事耿焊。” “怎么了遍搞?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵罗侯,是天一觀的道長。 經(jīng)常有香客問我溪猿,道長钩杰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任诊县,我火速辦了婚禮讲弄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘依痊。我一直安慰自己避除,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驹饺,像睡著了一般钳枕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赏壹,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天鱼炒,我揣著相機與錄音,去河邊找鬼蝌借。 笑死昔瞧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的菩佑。 我是一名探鬼主播自晰,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼稍坯!你這毒婦竟也來了酬荞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤瞧哟,失蹤者是張志新(化名)和其女友劉穎混巧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勤揩,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡咧党,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陨亡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傍衡。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖负蠕,靈堂內(nèi)的尸體忽然破棺而出蛙埂,到底是詐尸還是另有隱情,我是刑警寧澤遮糖,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布箱残,位于F島的核電站,受9級特大地震影響止吁,放射性物質(zhì)發(fā)生泄漏被辑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一敬惦、第九天 我趴在偏房一處隱蔽的房頂上張望习劫。 院中可真熱鬧笤成,春花似錦、人聲如沸围详。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春鸽粉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抓艳。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工触机, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玷或。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓儡首,卻偏偏與公主長得像,于是被迫代替她去往敵國和親偏友。 傳聞我的和親對象是個殘疾皇子蔬胯,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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

  • What is ARouterA framework for assisting in the renovatio...
    Horps閱讀 644評論 0 1
  • 組件化被越來越多的Android項目采用,而作為組件化的基礎(chǔ)——路由也是重中之重位他。本篇文章將詳細的分析阿里巴巴開源...
    胡奚冰閱讀 14,746評論 8 32
  • ARouter源碼解讀 以前看優(yōu)秀的開源項目氛濒,看到了頁面路由框架ARouter,心想頁面路由是個啥東東鹅髓,于是乎網(wǎng)上...
    陸元偉閱讀 513評論 0 1
  • 概述書接上回:ARouter源碼分析上[http://www.reibang.com/p/1e48c588d12...
    Horps閱讀 363評論 0 1
  • 本文章用于記錄筆者學(xué)習(xí) ARouter 源碼的過程舞竿,僅供參考,如有錯誤之處還望悉心指出迈勋,一起交流學(xué)習(xí)炬灭。 ARout...
    DevLocke閱讀 13,951評論 6 52