ARouter源碼詳解

一、ARouter

路由框架在大型項(xiàng)目中比較常見(jiàn),特別是在項(xiàng)目中擁有多個(gè) moudle 的時(shí)候。為了實(shí)現(xiàn)組件化,多個(gè) module 間的通信就不能直接以模塊間的引用來(lái)實(shí)現(xiàn),此時(shí)就需要依賴(lài)路由框架來(lái)實(shí)現(xiàn)模塊間的通信和解耦:sunglasses:

而 ARouter 就是一個(gè)用于幫助 Android App 進(jìn)行組件化改造的框架,支持模塊間的路由、通信、解耦

1、支持的功能

  1. 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn),并自動(dòng)注入?yún)?shù)到目標(biāo)頁(yè)面中
  2. 支持多模塊工程使用
  3. 支持添加多個(gè)攔截器,自定義攔截順序
  4. 支持依賴(lài)注入,可單獨(dú)作為依賴(lài)注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射關(guān)系按組分類(lèi)、多級(jí)管理,按需初始化
  8. 支持用戶(hù)指定全局降級(jí)與局部降級(jí)策略
  9. 頁(yè)面、攔截器、服務(wù)等組件均自動(dòng)注冊(cè)到框架
  10. 支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
  11. 支持獲取Fragment
  12. 完全支持Kotlin以及混編(配置見(jiàn)文末 其他#5)
  13. 支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊(cè))
  14. 支持生成路由文檔
  15. 提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標(biāo)類(lèi)

2、典型應(yīng)用

  1. 從外部URL映射到內(nèi)部頁(yè)面,以及參數(shù)傳遞與解析
  2. 跨模塊頁(yè)面跳轉(zhuǎn),模塊間解耦
  3. 攔截跳轉(zhuǎn)過(guò)程,處理登陸、埋點(diǎn)等邏輯
  4. 跨模塊API調(diào)用,通過(guò)控制反轉(zhuǎn)來(lái)做組件解耦

以上介紹來(lái)自于 ARouter 的 Github 官網(wǎng):README_CN

本文就基于其當(dāng)前(2020/10/04)ARouter 的最新版本,對(duì) ARouter 進(jìn)行一次全面的源碼解析和原理介紹,做到知其然也知其所以然,希望對(duì)你有所幫助????

dependencies {
    implementation 'com.alibaba:arouter-api:1.5.0'
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}
復(fù)制代碼

二、前言

假設(shè)存在一個(gè)包含多個(gè) moudle 的項(xiàng)目,在名為 user 的 moudle 中存在一個(gè) UserHomeActivity,其對(duì)應(yīng)的路由路徑/account/userHome。那么,當(dāng)我們要從其它 moudle 跳轉(zhuǎn)到該頁(yè)面時(shí),只需要指定 path 來(lái)跳轉(zhuǎn)即可

package github.leavesc.user

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/3 18:05
 * 描述:
 * GitHub:https://github.com/leavesC
 */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
    }

}

//其它頁(yè)面使用如下代碼來(lái)跳轉(zhuǎn)到 UserHomeActivity
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
復(fù)制代碼

只根據(jù)一個(gè) path,ARouter 是如何定位到特定的 Activity 的呢?

這就需要通過(guò)在編譯階段生成輔助代碼來(lái)實(shí)現(xiàn)了。我們都知道,想要跳轉(zhuǎn)到某個(gè) Activity,那么就需要拿到該 Activity 的 Class 對(duì)象才行。在編譯階段,ARouter 會(huì)根據(jù)我們?cè)O(shè)定的路由跳轉(zhuǎn)規(guī)則來(lái)自動(dòng)生成映射文件,映射文件中就包含了 path 和 ActivityClass 之間的對(duì)應(yīng)關(guān)系

例如,對(duì)于 UserHomeActivity,在編譯階段就會(huì)自動(dòng)生成以下輔助文件??梢钥吹?,ARouter$$Group$$account 類(lèi)中就將 path 和 ActivityClass 作為鍵值對(duì)保存到了 Map 中。ARouter 就是依靠此來(lái)進(jìn)行跳轉(zhuǎn)的

package com.alibaba.android.arouter.routes;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}
復(fù)制代碼

還有一個(gè)重點(diǎn)需要注意,就是這類(lèi)自動(dòng)生成的文件的包名路徑都是 com.alibaba.android.arouter.routes,且類(lèi)名前綴也是有特定規(guī)則的。雖然 ARouter$$Group$$account 類(lèi)實(shí)現(xiàn)了將對(duì)應(yīng)關(guān)系保存到 Map 的邏輯,但是 loadInto 方法還是需要由 ARouter 在運(yùn)行時(shí)來(lái)調(diào)用,那么 ARouter 就需要拿到 ARouter$$Group$$account 這個(gè)類(lèi)才行,而 ARouter 就是通過(guò)掃描 com.alibaba.android.arouter.routes這個(gè)包名路徑來(lái)獲取所有輔助文件的

ARouter 的基本實(shí)現(xiàn)思路就是:

  1. 開(kāi)發(fā)者自己維護(hù)特定 path特定的目標(biāo)類(lèi)之間的對(duì)應(yīng)關(guān)系,ARouter 只要求開(kāi)發(fā)者使用包含了 path 的 @Route 注解修飾目標(biāo)類(lèi)
  2. ARouter 在編譯階段通過(guò)注解處理器來(lái)自動(dòng)生成 path 和特定的目標(biāo)類(lèi)之間的對(duì)應(yīng)關(guān)系,即將 path 作為 key,將目標(biāo)類(lèi)的 Class 對(duì)象作為 value 之一存到 Map 之中
  3. 在運(yùn)行階段,應(yīng)用通過(guò) path 來(lái)發(fā)起請(qǐng)求,ARouter 根據(jù) path 從 Map 中取值,從而拿到目標(biāo)類(lèi)

三、初始化

ARouter 的一般是放在 Application 中調(diào)用 init 方法來(lái)完成初始化的,這里先來(lái)看下其初始化流程

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/4 18:05
 * 描述:
 * GitHub:https://github.com/leavesC
 */
class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            ARouter.openDebug()
            ARouter.openLog()
        }
        ARouter.init(this)
    }

}
復(fù)制代碼

ARouter 類(lèi)使用了單例模式,邏輯比較簡(jiǎn)單,因?yàn)?ARouter 類(lèi)只是負(fù)責(zé)對(duì)外暴露可以由外部調(diào)用的 API,大部分的實(shí)現(xiàn)邏輯還是轉(zhuǎn)交由 _ARouter 類(lèi)來(lái)完成

public final class ARouter {

    private volatile static ARouter instance = null;

    private ARouter() {
    }

    /**
     * Get instance of router. A
     * All feature U use, will be starts here.
     */
    public static ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (ARouter.class) {
                    if (instance == null) {
                        instance = new ARouter();
                    }
                }
            }
            return instance;
        }
    }

    /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) { //防止重復(fù)初始化
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            //通過(guò) _ARouter 來(lái)完成初始化
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

    ···

}
復(fù)制代碼

_ARouter 類(lèi)是包私有權(quán)限,也使用了單例模式,其 init(Application) 方法的重點(diǎn)就在于 LogisticsCenter.init(mContext, executor)

final class _ARouter {

    private volatile static _ARouter instance = null;

    private _ARouter() {
    }

    protected static _ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouterCore::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (_ARouter.class) {
                    if (instance == null) {
                        instance = new _ARouter();
                    }
                }
            }
            return instance;
        }
    }

    protected static synchronized boolean init(Application application) {
        mContext = application;
        //重點(diǎn)
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }

    ···

}
復(fù)制代碼

LogisticsCenter 就實(shí)現(xiàn)了前文說(shuō)的掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件的邏輯,即在進(jìn)行初始化的時(shí)候,我們就需要加載到當(dāng)前項(xiàng)目一共包含的所有 group,以及每個(gè) group 對(duì)應(yīng)的路由信息表,其主要邏輯是:

  1. 如果當(dāng)前開(kāi)啟了 debug 模式或者通過(guò)本地 SP 緩存判斷出 app 的版本前后發(fā)生了變化,那么就重新獲取全局路由信息,否則就從使用之前緩存到 SP 中的數(shù)據(jù)
  2. 獲取全局路由信息是一個(gè)比較耗時(shí)的操作,所以 ARouter 就通過(guò)將全局路由信息緩存到 SP 中來(lái)實(shí)現(xiàn)復(fù)用。但由于在開(kāi)發(fā)階段開(kāi)發(fā)者可能隨時(shí)就會(huì)添加新的路由表,而每次發(fā)布新版本正常來(lái)說(shuō)都是會(huì)加大應(yīng)用的版本號(hào)的,所以 ARouter 就只在開(kāi)啟了 debug 模式或者是版本號(hào)發(fā)生了變化的時(shí)候才會(huì)重新獲取路由信息
  3. 獲取到的路由信息中包含了在 com.alibaba.android.arouter.routes 這個(gè)包下自動(dòng)生成的輔助文件的全路徑,通過(guò)判斷路徑名的前綴字符串,就可以知道該類(lèi)文件對(duì)應(yīng)什么類(lèi)型,然后通過(guò)反射構(gòu)建不同類(lèi)型的對(duì)象,通過(guò)調(diào)用對(duì)象的方法將路由信息存到 Warehouse 的 Map 中。至此,整個(gè)初始化流程就結(jié)束了
public class LogisticsCenter {

    /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    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;

                //如果當(dāng)前開(kāi)啟了 debug 模式或者通過(guò)本地 SP 緩存判斷出 app 的版本前后發(fā)生了變化
                //那么就重新獲取路由信息,否則就從使用之前緩存到 SP 中的數(shù)據(jù)
                // 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.
                    //獲取 ROUTE_ROOT_PAKCAGE 包名路徑下包含的所有的 ClassName
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        //緩存到 SP 中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    //更新 App 的版本信息
                    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) {
                    //通過(guò) className 的前綴來(lái)判斷該 class 對(duì)應(yīng)的什么類(lèi)型,并同時(shí)緩存到 Warehouse 中
                    //1.IRouteRoot
                    //2.IInterceptorGroup
                    //3.IProviderGroup
                    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);
                    }
                }
            }

           ···

        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

}
復(fù)制代碼

對(duì)于第三步,可以舉個(gè)例子來(lái)加強(qiáng)理解。對(duì)于上文所講的 UserHomeActivity,其對(duì)應(yīng)的 path 是 /account/userHome,ARouter 默認(rèn)會(huì)將 path 的第一個(gè)單詞即 account 作為其 group,而且 UserHomeActivity 是放在名為 user 的 module 中

而 ARouter 在通過(guò)注解處理器生成輔助文件的時(shí)候,類(lèi)名就會(huì)根據(jù)以上信息來(lái)生成,所以最終就會(huì)生成以下兩個(gè)文件:

package com.alibaba.android.arouter.routes;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$user implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("account", ARouter$$Group$$account.class);
  }
}
復(fù)制代碼

package com.alibaba.android.arouter.routes;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}

復(fù)制代碼

LogisticsCenterinit 方法就會(huì)根據(jù)文件名的固定前綴 ARouter$$Root$$ 定位到 ARouter$$Root$$user 這個(gè)類(lèi),然后通過(guò)反射構(gòu)建出該對(duì)象,然后通過(guò)調(diào)用其 loadInto 方法將鍵值對(duì)保存到 Warehouse.groupsIndex 中。等到后續(xù)需要跳轉(zhuǎn)到 groupaccount 的頁(yè)面時(shí),就會(huì)再來(lái)反射調(diào)用 ARouter$$Group$$accountloadInto 方法,即按需加載,等到需要的時(shí)候再來(lái)獲取詳細(xì)的路由對(duì)應(yīng)信息

因?yàn)閷?duì)于一個(gè)大型的 App 來(lái)說(shuō),可能包含一百或者幾百個(gè)頁(yè)面,如果一次性將所有路由信息都加載到內(nèi)存中,對(duì)于內(nèi)存的壓力是比較大的,而用戶(hù)每次使用可能也只會(huì)打開(kāi)十幾個(gè)頁(yè)面,所以這里必須是按需加載

四、跳轉(zhuǎn)到 Activity

講完初始化流程,那就再來(lái)看下 ARouter 實(shí)現(xiàn) Activity 跳轉(zhuǎn)的流程

跳轉(zhuǎn)到 Activity 最簡(jiǎn)單的方式就是只指定 path:

    ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
復(fù)制代碼

build() 方法會(huì)通過(guò) ARouter 中轉(zhuǎn)調(diào)用到 _ARouterbuild() 方法,最終返回一個(gè) Postcard 對(duì)象

    /**
     * Build postcard by path and default group
     */
    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) {
                //用于路徑替換,這對(duì)于某些需要控制頁(yè)面跳轉(zhuǎn)流程的場(chǎng)景比較有用
                //例如,如果某個(gè)頁(yè)面需要登錄才可以展示的話(huà)
                //就可以通過(guò) PathReplaceService 將 path 替換 loginPagePath
                path = pService.forString(path);
            }
            //使用字符串 path 包含的第一個(gè)單詞作為 group
            return build(path, extractGroup(path));
        }
    }

    /**
     * Build postcard by path and group
     */
    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);
        }
    }
復(fù)制代碼

返回的 Postcard 對(duì)象可以用于傳入一些跳轉(zhuǎn)配置參數(shù),例如:攜帶參數(shù) mBundle、開(kāi)啟綠色通道 greenChannel 、跳轉(zhuǎn)動(dòng)畫(huà) optionsCompat

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;

}
復(fù)制代碼

Postcardnavigation() 方法又會(huì)調(diào)用到 _ARouter 的以下方法來(lái)完成 Activity 的跳轉(zhuǎn)。該方法邏輯上并不復(fù)雜,注釋也寫(xiě)得很清楚了

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ù)處理操作,可以通過(guò) onPretreatment 方法的返回值決定是否取消跳轉(zhuǎn)
            return null;
        }

        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            //沒(méi)有找到匹配的目標(biāo)類(lèi)
            //下面就執(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)類(lèi)
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            //沒(méi)有開(kāi)啟綠色通道,那么就還需要執(zhí)行所有攔截器
            //外部可以通過(guò)攔截器實(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 {
            //開(kāi)啟了綠色通道,直接跳轉(zhuǎn),不需要遍歷攔截器
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

    //由于本例子的目標(biāo)頁(yè)面是 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 對(duì)象
                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.
                //最終在主線(xiàn)程完成跳轉(zhuǎn)
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            ··· //省略其它類(lèi)型判斷
        }

        return null;
    }

}
復(fù)制代碼

navigation 方法的重點(diǎn)在于 LogisticsCenter.completion(postcard) 這一句代碼。在講 ARouter 初始化流程的時(shí)候有講到:等到后續(xù)需要跳轉(zhuǎn)到 groupaccount 的頁(yè)面時(shí),就會(huì)再來(lái)反射調(diào)用 ARouter$$Group$$accountloadInto 方法,即按需加載,等到需要的時(shí)候再來(lái)獲取詳細(xì)的路由對(duì)應(yīng)信息

completion 方法就是用來(lái)獲取詳細(xì)的路由對(duì)應(yīng)信息的。該方法會(huì)通過(guò) postcard 攜帶的 path 和 group 信息從 Warehouse 取值,如果值不為 null 的話(huà)就將信息保存到 postcard 中,如果值為 null 的話(huà)就拋出 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 說(shuō)明目標(biāo)類(lèi)不存在或者是該 group 還未加載過(guò)
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                //groupMeta 為 null,說(shuō)明 postcard 的 path 對(duì)應(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()));
                    }
                  //會(huì)執(zhí)行到這里,說(shuō)明此 group 還未加載過(guò),那么就來(lái)反射加載 group 對(duì)應(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());

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

五、跳轉(zhuǎn)到 Activity 并注入?yún)?shù)

ARouter 也支持在跳轉(zhuǎn)到 Activity 的同時(shí)向目標(biāo)頁(yè)面自動(dòng)注入?yún)?shù)

在跳轉(zhuǎn)的時(shí)候指定要攜帶的鍵值對(duì)參數(shù):

ARouter.getInstance().build(RoutePath.USER_HOME)
        .withLong(RoutePath.USER_HOME_PARAMETER_ID, 20)
        .withString(RoutePath.USER_HOME_PARAMETER_NAME, "leavesC")
        .navigation()

object RoutePath {

    const val USER_HOME = "/account/userHome"

    const val USER_HOME_PARAMETER_ID = "userHomeId"

    const val USER_HOME_PARAMETER_NAME = "userName"

}
復(fù)制代碼

在目標(biāo)頁(yè)面通過(guò) @Autowired 注解修飾變量。注解可以同時(shí)聲明其 name 參數(shù),用于和傳遞的鍵值對(duì)中的 key 對(duì)應(yīng)上,這樣 ARouter 才知道應(yīng)該向哪個(gè)變量賦值。如果沒(méi)有聲明 name 參數(shù),那么 name 參數(shù)就默認(rèn)和變量名相等

這樣,在我們調(diào)用 ARouter.getInstance().inject(this) 后,ARouter 就會(huì)自動(dòng)完成參數(shù)的賦值

package github.leavesc.user

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/4 14:05
 * 描述:
 * GitHub:https://github.com/leavesC
 */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {

    @Autowired(name = RoutePath.USER_HOME_PARAMETER_ID)
    @JvmField
    var userId: Long = 0

    @Autowired
    @JvmField
    var userName = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
        ARouter.getInstance().inject(this)
        tv_hint.text = "$userId $userName"
    }

}
復(fù)制代碼

ARouter 實(shí)現(xiàn)參數(shù)自動(dòng)注入也需要依靠注解處理器生成的輔助文件來(lái)實(shí)現(xiàn),即會(huì)生成以下的輔助代碼:

package github.leavesc.user;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {

  //用于實(shí)現(xiàn)序列化和反序列化
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    UserHomeActivity substitute = (UserHomeActivity)target;
    substitute.userId = substitute.getIntent().getLongExtra("userHomeId", substitute.userId);
    substitute.userName = substitute.getIntent().getStringExtra("userName");
  }
}

復(fù)制代碼

因?yàn)樵谔D(zhuǎn)到 Activity 時(shí)攜帶的參數(shù)也是需要放到 Intent 里的,所以 inject 方法也只是幫我們實(shí)現(xiàn)了從 Intent 取值然后向變量賦值的邏輯而已,這就要求相應(yīng)的變量必須是 public 的,這就是在 Kotlin 代碼中需要同時(shí)向變量加上 @JvmField注解的原因

現(xiàn)在來(lái)看下 ARouter 是如何實(shí)現(xiàn)參數(shù)自動(dòng)注入的,其起始方法就是:ARouter.getInstance().inject(this),其最終會(huì)調(diào)用到以下方法

final class _ARouter {

    static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
    }

}
復(fù)制代碼

ARouter 通過(guò)控制反轉(zhuǎn)的方式拿到 AutowiredService 對(duì)應(yīng)的實(shí)現(xiàn)類(lèi) AutowiredServiceImpl的實(shí)例對(duì)象,然后調(diào)用其 autowire 方法完成參數(shù)注入

由于生成的參數(shù)注入輔助類(lèi)的類(lèi)名具有固定的包名和類(lèi)名,即包名和目標(biāo)類(lèi)所在包名一致,類(lèi)名是目標(biāo)類(lèi)類(lèi)名+ $$ARouter$$Autowired,所以在 AutowiredServiceImpl 中就可以根據(jù)傳入的 instance 參數(shù)和反射來(lái)生成輔助類(lèi)對(duì)象,最終調(diào)用其 inject 方法完成參數(shù)注入

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache<String, ISyringe> classCache;
    private List<String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            //如果在白名單中了的話(huà),那么就不再執(zhí)行參數(shù)注入
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                //完成參數(shù)注入
                autowiredHelper.inject(instance);
                //緩存起來(lái),避免重復(fù)反射
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            //如果參數(shù)注入過(guò)程拋出異常,那么就將其加入白名單中
            blackList.add(className);    // This instance need not autowired.
        }
    }
}
復(fù)制代碼

六、控制反轉(zhuǎn)

上一節(jié)所講的跳轉(zhuǎn)到 Activity 并自動(dòng)注入?yún)?shù)屬于依賴(lài)注入的一種,ARouter 同時(shí)也支持控制反轉(zhuǎn):通過(guò)接口來(lái)獲取其實(shí)現(xiàn)類(lèi)實(shí)例

例如,假設(shè)存在一個(gè) ISayHelloService 接口,我們需要拿到其實(shí)現(xiàn)類(lèi)實(shí)例,但是不希望在使用的時(shí)候和特定的實(shí)現(xiàn)類(lèi) SayHelloService 綁定在一起從而造成強(qiáng)耦合,此時(shí)就可以使用 ARouter 的控制反轉(zhuǎn)功能,但這也要求 ISayHelloService 接口繼承了 IProvider 接口才行

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/4 13:49
 * 描述:
 * GitHub:https://github.com/leavesC
 */
interface ISayHelloService : IProvider {

    fun sayHello()

}

@Route(path = RoutePath.SERVICE_SAY_HELLO)
class SayHelloService : ISayHelloService {

    override fun init(context: Context) {

    }

    override fun sayHello() {
        Log.e("SayHelloService", "$this sayHello")
    }

}
復(fù)制代碼

在使用的時(shí)候直接傳遞 ISayHelloService 的 Class 對(duì)象即可,ARouter 會(huì)將 SayHelloService以單例模式的形式返回,無(wú)需開(kāi)發(fā)者手動(dòng)去構(gòu)建 SayHelloService 對(duì)象,從而達(dá)到解耦的目的

ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
復(fù)制代碼

和實(shí)現(xiàn) Activity 跳轉(zhuǎn)的時(shí)候一樣,ARouter 也會(huì)自動(dòng)生成以下幾個(gè)文件,包含了路由表的映射關(guān)系

package com.alibaba.android.arouter.routes;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/sayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhelloservice", "account", null, -1, -2147483648));
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$user implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("github.leavesc.user.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayHelloService", "account", null, -1, -2147483648));
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$user implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("account", ARouter$$Group$$account.class);
  }
}
復(fù)制代碼

這里再來(lái)看下其具體的實(shí)現(xiàn)原理

在講初始化流程的時(shí)候有講到,LogisticsCenter 實(shí)現(xiàn)了掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件的邏輯。所以,最終 Warehouse 中就會(huì)在初始化的時(shí)候拿到以下數(shù)據(jù)

Warehouse.groupsIndex:

  • account -> class com.alibaba.android.arouter.routes.ARouter$$Group$$account

Warehouse.providersIndex:

  • github.leavesc.user.ISayHelloService -> RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayHelloService", "account", null, -1, -2147483648)

ARouter.getInstance().navigation(ISayHelloService::class.java) 最終會(huì)中轉(zhuǎn)調(diào)用到 _ARouter的以下方法

    protected <T> T navigation(Class<? extends T> service) {
        try {
            //從 Warehouse.providersIndex 取值拿到 RouteMeta 中存儲(chǔ)的 path 和 group
            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;
            }
           //重點(diǎn)
            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }
復(fù)制代碼

LogisticsCenter.completion(postcard) 方法的流程和之前講解的差不多,只是在獲取對(duì)象實(shí)例的時(shí)候同時(shí)將實(shí)例緩存起來(lái),留待之后復(fù)用,至此就完成了控制反轉(zhuǎn)的流程了

    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should complete by this method.
     */
    public synchronized static void completion(Postcard postcard) {
       ... //省略之前已經(jīng)講解過(guò)的代碼   

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

        switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    //拿到 SayHelloService Class 對(duì)象
                    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
                        //instance 等于 null 說(shuō)明是第一次取值
                        //那么就通過(guò)反射構(gòu)建 SayHelloService 對(duì)象,然后將之緩存到 Warehouse.providers 中
                        //所以通過(guò)控制反轉(zhuǎn)獲取的對(duì)象在應(yīng)用的整個(gè)生命周期內(nèi)只會(huì)有一個(gè)實(shí)例
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    //將獲取到的實(shí)例存起來(lái)
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }

    }
復(fù)制代碼

七、攔截器

ARouter 的攔截器對(duì)于某些需要控制頁(yè)面跳轉(zhuǎn)流程的業(yè)務(wù)邏輯來(lái)說(shuō)是十分有用的功能。例如,用戶(hù)如果要跳轉(zhuǎn)到個(gè)人資料頁(yè)面時(shí),我們就可以通過(guò)攔截器來(lái)判斷用戶(hù)是否處于已登錄狀態(tài),還未登錄的話(huà)就可以攔截該請(qǐng)求,然后自動(dòng)為用戶(hù)打開(kāi)登錄頁(yè)面

我們可以同時(shí)設(shè)定多個(gè)攔截器,每個(gè)攔截器設(shè)定不同的優(yōu)先級(jí)

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/5 11:49
 * 描述:
 * GitHub:https://github.com/leavesC
 */
@Interceptor(priority = 100, name = "啥也不做的攔截器")
class NothingInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        //不攔截,任其跳轉(zhuǎn)
        callback.onContinue(postcard)
    }

}

@Interceptor(priority = 200, name = "登陸攔截器")
class LoginInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        if (postcard.path == RoutePath.USER_HOME) {
            //攔截
            callback.onInterrupt(null)
            //跳轉(zhuǎn)到登陸頁(yè)
            ARouter.getInstance().build(RoutePath.USER_LOGIN).navigation()
        } else {
            //不攔截,任其跳轉(zhuǎn)
            callback.onContinue(postcard)
        }
    }

}
復(fù)制代碼

這樣,當(dāng)我們執(zhí)行 ARouter.getInstance().build(RoutePath.USER_HOME).navigation() 想要跳轉(zhuǎn)的時(shí)候,就會(huì)發(fā)現(xiàn)打開(kāi)的其實(shí)是登錄頁(yè) RoutePath.USER_LOGIN

來(lái)看下攔截器是如何實(shí)現(xiàn)的

對(duì)于以上的兩個(gè)攔截器,會(huì)生成以下的輔助文件。輔助文件會(huì)拿到所有我們自定義的攔截器實(shí)現(xiàn)類(lèi)并根據(jù)優(yōu)先級(jí)高低存到 Map 中

package com.alibaba.android.arouter.routes;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$user implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(100, NothingInterceptor.class);
    interceptors.put(200, LoginInterceptor.class);
  }
}
復(fù)制代碼

而這些攔截器一樣是會(huì)在初始化的時(shí)候,通過(guò)LogisticsCenter.init方法存到 Warehouse.interceptorsIndex

 /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

        ···

        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
                        //拿到自定義的攔截器實(shí)現(xiàn)類(lèi)
                        ((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);
                    }
                }

          ···

    }
復(fù)制代碼

然后,在 _ARouternavigation 方法中,如何判斷到此次路由請(qǐng)求沒(méi)有開(kāi)啟綠色通道模式的話(huà),那么就會(huì)將此次請(qǐng)求轉(zhuǎn)交給 interceptorService,讓其去遍歷每個(gè)攔截器

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) {

        ···

        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(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 {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

}
復(fù)制代碼

interceptorService 變量屬于 InterceptorService 接口類(lèi)型,該接口的實(shí)現(xiàn)類(lèi)是 InterceptorServiceImpl,ARouter內(nèi)部在初始化的過(guò)程中也是根據(jù)控制反轉(zhuǎn)的方式來(lái)拿到 interceptorService 這個(gè)實(shí)例的

InterceptorServiceImpl 的主要邏輯是:

  1. 在第一次獲取 InterceptorServiceImpl 實(shí)例的時(shí)候,其 init 方法會(huì)馬上被調(diào)用,該方法內(nèi)部會(huì)交由線(xiàn)程池來(lái)執(zhí)行,通過(guò)反射生成每個(gè)攔截器對(duì)象,并調(diào)用每個(gè)攔截器的 init 方法來(lái)完成攔截器的初始化,并將每個(gè)攔截器對(duì)象都存到 Warehouse.interceptors 中。如果初始化完成了,則喚醒等待在 interceptorInitLock 上的線(xiàn)程
  2. 當(dāng)攔截器邏輯被觸發(fā),即 doInterceptions 方法被調(diào)用時(shí),如果此時(shí)第一個(gè)步驟還未執(zhí)行完的話(huà),則會(huì)通過(guò) checkInterceptorsInitStatus()方法等待第一個(gè)步驟執(zhí)行完成。如果十秒內(nèi)都未完成的話(huà),則走失敗流程直接返回
  3. 在線(xiàn)程池中遍歷攔截器列表,如果有某個(gè)攔截器攔截了請(qǐng)求的話(huà)則調(diào)用 callback.onInterrupt方法通知外部,否則的話(huà)則調(diào)用 callback.onContinue() 方法繼續(xù)跳轉(zhuǎn)邏輯
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    @Override
    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    //遍歷攔截器列表,通過(guò)反射構(gòu)建對(duì)象并初始化
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            //存起來(lái)
                            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();
                    }
                }
            }
        });
    }

    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

            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 {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            //大于 0 說(shuō)明此次請(qǐng)求被某個(gè)攔截器攔截了,走失敗流程
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    /**
     * Excute interceptor
     *
     * @param index    current interceptor index
     * @param counter  interceptor counter
     * @param postcard routeMeta
     */
    private static void _excute(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();
                    _excute(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 excute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // 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());
//                    }
                }
            });
        }
    }

    private static void checkInterceptorsInitStatus() {
        synchronized (interceptorInitLock) {
            while (!interceptorHasInit) {
                try {
                    interceptorInitLock.wait(10 * 1000);
                } catch (InterruptedException e) {
                    throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
                }
            }
        }
    }

}
復(fù)制代碼

八、注解處理器

通篇讀下來(lái),讀者應(yīng)該能夠感受到注解處理器在 ARouter 中起到了很大的作用,依靠注解處理器生成的輔助文件,ARouter 才能完成參數(shù)自動(dòng)注入等功能。這里就再來(lái)介紹下 ARouter 關(guān)于注解處理器的實(shí)現(xiàn)原理

APT(Annotation Processing Tool) 即注解處理器,是一種注解處理工具,用來(lái)在編譯期掃描和處理注解,通過(guò)注解來(lái)生成 Java 文件。即以注解作為橋梁,通過(guò)預(yù)先規(guī)定好的代碼生成規(guī)則來(lái)自動(dòng)生成 Java 文件。此類(lèi)注解框架的代表有 ButterKnife、Dragger2、EventBus

Java API 已經(jīng)提供了掃描源碼并解析注解的框架,開(kāi)發(fā)者可以通過(guò)繼承 AbstractProcessor 類(lèi)來(lái)實(shí)現(xiàn)自己的注解解析邏輯。APT 的原理就是在注解了某些代碼元素(如字段、函數(shù)、類(lèi)等)后,在編譯時(shí)編譯器會(huì)檢查 AbstractProcessor 的子類(lèi),并且自動(dòng)調(diào)用其 process() 方法,然后將添加了指定注解的所有代碼元素作為參數(shù)傳遞給該方法,開(kāi)發(fā)者再根據(jù)注解元素在編譯期輸出對(duì)應(yīng)的 Java 代碼

關(guān)于 APT 技術(shù)的原理和應(yīng)用可以看這篇文章:Android APT 實(shí)例講解

ARouter 源碼中和注解處理器相關(guān)的 module 有兩個(gè):

  • arouter-annotation。Java Module,包含了像 Autowired、Interceptor 這些注解以及 RouteMeta 等 JavaBean
  • arouter-compiler。Android Module,包含了多個(gè) AbstractProcessor 的實(shí)現(xiàn)類(lèi)用于生成代碼

這里主要來(lái)看 arouter-compiler,這里以自定義的攔截器 NothingInterceptor 作為例子

package github.leavesc.user

/**
 * 作者:leavesC
 * 時(shí)間:2020/10/5 11:49
 * 描述:
 * GitHub:https://github.com/leavesC
 */
@Interceptor(priority = 100, name = "啥也不做的攔截器")
class NothingInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        //不攔截,任其跳轉(zhuǎn)
        callback.onContinue(postcard)
    }

}
復(fù)制代碼

生成的輔助文件:

package com.alibaba.android.arouter.routes;

import com.alibaba.android.arouter.facade.template.IInterceptor;
import com.alibaba.android.arouter.facade.template.IInterceptorGroup;
import github.leavesc.user.NothingInterceptor;
import java.lang.Class;
import java.lang.Integer;
import java.lang.Override;
import java.util.Map;

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$user implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(100, NothingInterceptor.class);
  }
}
復(fù)制代碼

那么,生成的輔助文件我們就要包含以下幾個(gè)元素:

  1. 包名
  2. 導(dǎo)包
  3. 注釋
  4. 實(shí)現(xiàn)類(lèi)及繼承的接口
  5. 包含的方法及方法參數(shù)
  6. 方法體
  7. 修飾符

如果通過(guò)硬編碼的形式,即通過(guò)拼接字符串的方式來(lái)生成以上代碼也是可以的,但是這樣會(huì)使得代碼不好維護(hù)且可讀性很低,所以 ARouter 是通過(guò) JavaPoet 這個(gè)開(kāi)源庫(kù)來(lái)生成代碼的。JavaPoet是 square 公司開(kāi)源的 Java 代碼生成框架,可以很方便地通過(guò)其提供的 API 來(lái)生成指定格式(修飾符、返回值、參數(shù)、函數(shù)體等)的代碼

攔截器對(duì)應(yīng)的 AbstractProcessor 子類(lèi)就是 InterceptorProcessor,其主要邏輯是:

  1. 在 process 方法中通過(guò) RoundEnvironment 拿到所有使用了 @Interceptor 注解進(jìn)行修飾的代碼元素 elements,然后遍歷所有 item
  2. 判斷每個(gè) item 是否繼承了 IInterceptor 接口,是的話(huà)則說(shuō)明該 item 就是我們要找的攔截器實(shí)現(xiàn)類(lèi)
  3. 獲取每個(gè) item 包含的 @Interceptor 注解對(duì)象,根據(jù)我們?yōu)橹O(shè)定的優(yōu)先級(jí) priority,將每個(gè) item 按順序存到 interceptors 中
  4. 如果存在兩個(gè)攔截器的優(yōu)先級(jí)相同,那么就拋出異常
  5. 將所有攔截器按順序存入 interceptors 后,通過(guò) JavaPoet 提供的 API 來(lái)生成包名、導(dǎo)包、注釋、實(shí)現(xiàn)類(lèi)等多個(gè)代碼元素,并最終生成一個(gè)完整的類(lèi)文件
@AutoService(Processor.class)
@SupportedAnnotationTypes(ANNOTATION_TYPE_INTECEPTOR)
public class InterceptorProcessor extends BaseProcessor {

    //用于保存攔截器,按照優(yōu)先級(jí)高低進(jìn)行排序
    private Map<Integer, Element> interceptors = new TreeMap<>();

    private TypeMirror iInterceptor = null;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        iInterceptor = elementUtils.getTypeElement(Consts.IINTERCEPTOR).asType();

        logger.info(">>> InterceptorProcessor init. <<<");
    }

    /**
     * {@inheritDoc}
     *
     * @param annotations
     * @param roundEnv
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            //拿到所有使用了 @Interceptor 進(jìn)行修飾的代碼元素
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
            try {
                parseInterceptors(elements);
            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

    /**
     * Parse tollgate.
     *
     * @param elements elements of tollgate.
     */
    private void parseInterceptors(Set<? extends Element> elements) throws IOException {
        if (CollectionUtils.isNotEmpty(elements)) {
            logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");

            // Verify and cache, sort incidentally.
            for (Element element : elements) {
                //判斷使用了 @Interceptor 進(jìn)行修飾的代碼元素是否同時(shí)實(shí)現(xiàn)了 com.alibaba.android.arouter.facade.template.IInterceptor 這個(gè)接口
                //兩者缺一不可
                if (verify(element)) {  // Check the interceptor meta
                    logger.info("A interceptor verify over, its " + element.asType());
                    Interceptor interceptor = element.getAnnotation(Interceptor.class);

                    Element lastInterceptor = interceptors.get(interceptor.priority());
                    if (null != lastInterceptor) { // Added, throw exceptions
                        //不為 null 說(shuō)明存在兩個(gè)攔截器其優(yōu)先級(jí)相等,這是不允許的,直接拋出異常
                        throw new IllegalArgumentException(
                                String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
                                        interceptor.priority(),
                                        lastInterceptor.getSimpleName(),
                                        element.getSimpleName())
                        );
                    }
                    //將攔截器按照優(yōu)先級(jí)高低進(jìn)行排序保存
                    interceptors.put(interceptor.priority(), element);
                } else {
                    logger.error("A interceptor verify failed, its " + element.asType());
                }
            }

            // Interface of ARouter.
            //拿到 com.alibaba.android.arouter.facade.template.IInterceptor 這個(gè)接口的類(lèi)型抽象
            TypeElement type_ITollgate = elementUtils.getTypeElement(IINTERCEPTOR);
            //拿到 com.alibaba.android.arouter.facade.template.IInterceptorGroup 這個(gè)接口的類(lèi)型抽象
            TypeElement type_ITollgateGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);

            /**
             *  Build input type, format as :
             *
             *  ```Map<Integer, Class<? extends ITollgate>>```
             */
            //生成對(duì) Map<Integer, Class<? extends IInterceptor>> 這段代碼的抽象封裝
            ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(Integer.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
                    )
            );

            // Build input param name.
            //生成 loadInto 方法的入?yún)?shù) interceptors
            ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();

            // Build method : 'loadInto'
            //生成 loadInto 方法
            MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(tollgateParamSpec);

            // Generate
            if (null != interceptors && interceptors.size() > 0) {
                // Build method body
                for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
                    //遍歷每個(gè)攔截器,生成 interceptors.put(100, NothingInterceptor.class); 這類(lèi)型的代碼
                    loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
                }
            }

            // Write to disk(Write file even interceptors is empty.)
            //包名固定是 PACKAGE_OF_GENERATE_FILE,即 com.alibaba.android.arouter.routes
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName) //設(shè)置類(lèi)名
                            .addModifiers(PUBLIC) //添加 public 修飾符
                            .addJavadoc(WARNING_TIPS) //添加注釋
                            .addMethod(loadIntoMethodOfTollgateBuilder.build()) //添加 loadInto 方法
                            .addSuperinterface(ClassName.get(type_ITollgateGroup)) //最后生成的類(lèi)同時(shí)實(shí)現(xiàn)了 IInterceptorGroup 接口
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Interceptor group write over. <<<");
        }
    }

    /**
     * Verify inteceptor meta
     *
     * @param element Interceptor taw type
     * @return verify result
     */
    private boolean verify(Element element) {
        Interceptor interceptor = element.getAnnotation(Interceptor.class);
        // It must be implement the interface IInterceptor and marked with annotation Interceptor.
        return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
    }
}
復(fù)制代碼

九、結(jié)尾

ARouter 的實(shí)現(xiàn)原理和源碼解析都講得差不多了,文本應(yīng)該講得挺全面的了,那么下一篇就再來(lái)進(jìn)入實(shí)戰(zhàn)篇吧,自己來(lái)動(dòng)手實(shí)現(xiàn)一個(gè) ARouter ????

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容