三方庫源碼筆記(3)- ARouter 源碼詳解

對(duì)于 Android Developer 來說埃疫,很多開源庫都是屬于開發(fā)必備的知識(shí)點(diǎn)也切,從使用方式到實(shí)現(xiàn)原理再到源碼解析檬姥,這些都需要我們有一定程度的了解和運(yùn)用能力涧卵。所以我打算來寫一系列關(guān)于開源庫源碼解析實(shí)戰(zhàn)演練的文章蟹瘾,初定的目標(biāo)是 EventBus圾浅、ARouter、LeakCanary憾朴、Retrofit狸捕、Glide、OkHttp众雷、Coil 等七個(gè)知名開源庫灸拍,希望對(duì)你有所幫助 ????

一、ARouter

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

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

支持的功能

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

典型應(yīng)用

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

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

本文就基于以下版本壳澳,對(duì) ARouter 進(jìn)行一次全面的源碼解析和原理介紹,做到知其然也知其所以然茫经,希望對(duì)你有所幫助 ????

dependencies {
    implementation 'com.alibaba:arouter-api:1.5.0'
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}

二巷波、前言

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

package github.leavesc.user

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {

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

}

//其它頁面使用如下代碼來跳轉(zhuǎn)到 UserHomeActivity
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()

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

例如芯咧,對(duì)于 UserHomeActivity牙捉,在編譯階段就會(huì)自動(dòng)生成以下輔助文件』5常可以看到鹃共,ARouter$$Group$$account 類中就將 path 和 ActivityClass 作為鍵值對(duì)保存到了 Map 中。ARouter 就是依靠此來進(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));
  }
}

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

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

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

三喝检、初始化

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

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
class MyApp : Application() {

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

}

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

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.");
            //通過 _ARouter 來完成初始化
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }
    
    ···
    
}

_ARouter 類是包私有權(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;
    }
    
    ···
    
}

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

  1. 如果當(dāng)前開啟了 debug 模式或者通過本地 SP 緩存判斷出 app 的版本前后發(fā)生了變化雁仲,那么就重新獲取全局路由信息,否則就還是使用之前緩存到 SP 中的數(shù)據(jù)
  2. 獲取全局路由信息是一個(gè)比較耗時(shí)的操作拧咳,所以 ARouter 就通過將全局路由信息緩存到 SP 中來實(shí)現(xiàn)復(fù)用伯顶。但由于在開發(fā)階段開發(fā)者可能隨時(shí)就會(huì)添加新的路由表,而每次發(fā)布新版本正常來說都是會(huì)加大應(yīng)用的版本號(hào)的骆膝,所以 ARouter 就只在開啟了 debug 模式或者是版本號(hào)發(fā)生了變化的時(shí)候才會(huì)重新獲取路由信息
  3. 獲取到的路由信息中包含了在 com.alibaba.android.arouter.routes 這個(gè)包下自動(dòng)生成的輔助文件的全路徑祭衩,通過判斷路徑名的前綴字符串,就可以知道該類文件對(duì)應(yīng)什么類型阅签,然后通過反射構(gòu)建不同類型的對(duì)象掐暮,通過調(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)前開啟了 debug 模式或者通過本地 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) {
                    //通過 className 的前綴來判斷該 class 對(duì)應(yīng)的什么類型,并同時(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() + "]");
        }
    }
    
}

對(duì)于第三步养交,可以舉個(gè)例子來加強(qiáng)理解

對(duì)于上文所講的 UserHomeActivity精算,放在名為 user 的 module 中,按照 ARouter 的要求碎连,在 gradle 文件中為該 module 聲明了以下配置灰羽,將 projectName 作為參數(shù)值。UserHomeActivity 對(duì)應(yīng)的 path 是 /account/userHome,ARouter 默認(rèn)會(huì)將 path 的第一個(gè)單詞即 account 作為其 group

javaCompileOptions {
    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }
}

ARouter 在通過注解處理器生成輔助文件的時(shí)候廉嚼,類名就會(huì)根據(jù)以上信息來生成玫镐,所以最終就會(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);
  }
}

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

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

因?yàn)閷?duì)于一個(gè)大型的 App 來說,可能包含幾十甚至幾百個(gè)頁面捂寿,如果一次性將所有路由信息都加載到內(nèi)存中口四,對(duì)于內(nèi)存的壓力是比較大的,而用戶每次使用可能也只會(huì)打開十幾個(gè)頁面秦陋,所以這是就實(shí)現(xiàn)了按需加載

四、跳轉(zhuǎn)到 Activity

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

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

ARouter.getInstance().build(RoutePath.USER_HOME).navigation()

build() 方法會(huì)通過 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ì)于某些需要控制頁面跳轉(zhuǎn)流程的場(chǎng)景比較有用
            //例如旷赖,如果某個(gè)頁面需要登錄才可以展示的話
            //就可以通過 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);
    }
}

返回的 Postcard 對(duì)象可以用于傳入一些跳轉(zhuǎn)配置參數(shù)顺又,例如:攜帶參數(shù) mBundle各谚、開啟綠色通道 greenChannel 榛泛、跳轉(zhuǎn)動(dòng)畫 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;
    
}

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

final class _ARouter {

    /**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            //用于執(zhí)行跳轉(zhuǎn)前的預(yù)處理操作甸赃,可以通過 onPretreatment 方法的返回值決定是否取消跳轉(zhuǎn)
            return null;
        }

        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            //沒有找到匹配的目標(biāo)類
            //下面就執(zhí)行一些提示操作和事件回調(diào)通知
            
            logger.warning(Consts.TAG, ex.getMessage());
            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }
            if (null != callback) {
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }
            return null;
        }

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

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            //沒有開啟綠色通道钥屈,那么就還需要執(zhí)行所有攔截器
            //外部可以通過攔截器實(shí)現(xiàn):控制是否允許跳轉(zhuǎn)咽袜、更改跳轉(zhuǎn)參數(shù)等邏輯
            
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    //攔截器允許跳轉(zhuǎn)
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            //開啟了綠色通道妨马,直接跳轉(zhuǎn)咐熙,不需要遍歷攔截器
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }
    
    //由于本例子的目標(biāo)頁面是 Activity弱恒,所以只看 ACTIVITY 即可
    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;
        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                //Destination 就是指向目標(biāo) Activity 的 class 對(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.
                //最終在主線程完成跳轉(zhuǎn)
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

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

        return null;
    }

}

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

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

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

    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    //為 null 說明目標(biāo)類不存在或者是該 group 還未加載過
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
            //groupMeta 為 null师崎,說明 postcard 的 path 對(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í)行到這里,說明此 group 還未加載過,那么就來反射加載 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());

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

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

ARouter 也支持在跳轉(zhuǎn)到 Activity 的同時(shí)向目標(biāo)頁面自動(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"

}

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

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

package github.leavesc.user

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
@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"
    }

}

ARouter 實(shí)現(xiàn)參數(shù)自動(dòng)注入也需要依靠注解處理器生成的輔助文件來實(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");
  }
}

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

現(xiàn)在來看下 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);
        }
    }
    
}

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

由于生成的參數(shù)注入輔助類的類名具有固定的包名和類名屉栓,即包名和目標(biāo)類所在包名一致,類名是目標(biāo)類類名+ $$ARouter$$Autowired耸袜,所以在 AutowiredServiceImpl 中就可以根據(jù)傳入的 instance 參數(shù)和反射來生成輔助類對(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 {
            //如果在白名單中了的話,那么就不再執(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);
                //緩存起來堤框,避免重復(fù)反射
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            //如果參數(shù)注入過程拋出異常域滥,那么就將其加入白名單中
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

六、控制反轉(zhuǎn)

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

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

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
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")
    }

}

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

ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()

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

這里再來看下其具體的實(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;
    }
}

LogisticsCenter.completion(postcard) 方法的流程和之前講解的差不多,只是在獲取對(duì)象實(shí)例的時(shí)候同時(shí)將實(shí)例緩存起來,留待之后復(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)講解過的代碼   

   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 說明是第一次取值
                    //那么就通過反射構(gòu)建 SayHelloService 對(duì)象岳守,然后將之緩存到 Warehouse.providers 中
                    //所以通過控制反轉(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í)例存起來
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }

}

七、攔截器

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

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

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
@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)到登陸頁
            ARouter.getInstance().build(RoutePath.USER_LOGIN).navigation()
        } else {
            //不攔截臀规,任其跳轉(zhuǎn)
            callback.onContinue(postcard)
        }
    }

}

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

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

對(duì)于以上的兩個(gè)攔截器塔嬉,會(huì)生成以下的輔助文件。輔助文件會(huì)拿到所有我們自定義的攔截器實(shí)現(xiàn)類并根據(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);
  }
}

而這些攔截器一樣是會(huì)在初始化的時(shí)候约啊,通過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)類
            ((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);
        }
    }

    ···

}

然后邑遏,在 _ARouternavigation 方法中,如何判斷到此次路由請(qǐng)求沒有開啟綠色通道模式的話恰矩,那么就會(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;
    }
    
}

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

InterceptorServiceImpl 的主要邏輯是:

  1. 在第一次獲取 InterceptorServiceImpl 實(shí)例的時(shí)候,其 init 方法會(huì)馬上被調(diào)用俩檬,該方法內(nèi)部會(huì)交由線程池來執(zhí)行萎胰,通過反射生成每個(gè)攔截器對(duì)象,并調(diào)用每個(gè)攔截器的 init 方法來完成攔截器的初始化棚辽,并將每個(gè)攔截器對(duì)象都存到 Warehouse.interceptors 中技竟。如果初始化完成了,則喚醒等待在 interceptorInitLock 上的線程
  2. 當(dāng)攔截器邏輯被觸發(fā)屈藐,即 doInterceptions 方法被調(diào)用時(shí)榔组,如果此時(shí)第一個(gè)步驟還未執(zhí)行完的話,則會(huì)通過 checkInterceptorsInitStatus()方法等待第一個(gè)步驟執(zhí)行完成联逻。如果十秒內(nèi)都未完成的話搓扯,則走失敗流程直接返回
  3. 在線程池中遍歷攔截器列表,如果有某個(gè)攔截器攔截了請(qǐng)求的話則調(diào)用 callback.onInterrupt方法通知外部包归,否則的話則調(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)) {
                    //遍歷攔截器列表锨推,通過反射構(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);
                            //存起來
                            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 說明此次請(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() + "]");
                }
            }
        }
    }
    
}

八椎椰、注解處理器

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

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

Java API 已經(jīng)提供了掃描源碼并解析注解的框架本辐,開發(fā)者可以通過繼承 AbstractProcessor 類來實(shí)現(xiàn)自己的注解解析邏輯。APT 的原理就是在注解了某些代碼元素(如字段医增、函數(shù)慎皱、類等)后,在編譯時(shí)編譯器會(huì)檢查 AbstractProcessor 的子類叶骨,并且自動(dòng)調(diào)用其 process() 方法茫多,然后將添加了指定注解的所有代碼元素作為參數(shù)傳遞給該方法,開發(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)類用于生成代碼

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

package github.leavesc.user

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
@Interceptor(priority = 100, name = "啥也不做的攔截器")
class NothingInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

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

}

生成的輔助文件:

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

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

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

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

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

  1. 在 process 方法中通過 RoundEnvironment 拿到所有使用了 @Interceptor 注解進(jìn)行修飾的代碼元素 elements洋访,然后遍歷所有 item
  2. 判斷每個(gè) item 是否繼承了 IInterceptor 接口,是的話則說明該 item 就是我們要找的攔截器實(shí)現(xiàn)類
  3. 獲取每個(gè) item 包含的 @Interceptor 注解對(duì)象谴餐,根據(jù)我們?yōu)橹O(shè)定的優(yōu)先級(jí) priority姻政,將每個(gè) item 按順序存到 interceptors 中
  4. 如果存在兩個(gè)攔截器的優(yōu)先級(jí)相同,那么就拋出異常
  5. 將所有攔截器按順序存入 interceptors 后岂嗓,通過 JavaPoet 提供的 API 來生成包名汁展、導(dǎo)包、注釋厌殉、實(shí)現(xiàn)類等多個(gè)代碼元素食绿,并最終生成一個(gè)完整的類文件
@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 說明存在兩個(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è)接口的類型抽象
            TypeElement type_ITollgate = elementUtils.getTypeElement(IINTERCEPTOR);
            //拿到 com.alibaba.android.arouter.facade.template.IInterceptorGroup 這個(gè)接口的類型抽象
            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); 這類型的代碼
                    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è)置類名
                            .addModifiers(PUBLIC) //添加 public 修飾符
                            .addJavadoc(WARNING_TIPS) //添加注釋
                            .addMethod(loadIntoMethodOfTollgateBuilder.build()) //添加 loadInto 方法
                            .addSuperinterface(ClassName.get(type_ITollgateGroup)) //最后生成的類同時(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);
    }
}

九铲汪、結(jié)尾

ARouter 的實(shí)現(xiàn)原理和源碼解析都講得差不多了,自認(rèn)還是講得挺全面的罐柳,那么下一篇就再來進(jìn)入實(shí)戰(zhàn)篇吧掌腰,自己來動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)易版本的 ARouter ????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市张吉,隨后出現(xiàn)的幾起案子齿梁,更是在濱河造成了極大的恐慌,老刑警劉巖肮蛹,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件士飒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蔗崎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門扰藕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缓苛,“玉大人,你說我怎么就攤上這事邓深∥辞牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵芥备,是天一觀的道長(zhǎng)冬耿。 經(jīng)常有香客問我,道長(zhǎng)萌壳,這世上最難降的妖魔是什么亦镶? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任日月,我火速辦了婚禮,結(jié)果婚禮上缤骨,老公的妹妹穿的比我還像新娘爱咬。我一直安慰自己,他們只是感情好绊起,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布精拟。 她就那樣靜靜地躺著,像睡著了一般虱歪。 火紅的嫁衣襯著肌膚如雪蜂绎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天笋鄙,我揣著相機(jī)與錄音师枣,去河邊找鬼。 笑死局装,一個(gè)胖子當(dāng)著我的面吹牛坛吁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铐尚,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拨脉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了宣增?” 一聲冷哼從身側(cè)響起玫膀,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爹脾,沒想到半個(gè)月后帖旨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灵妨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年解阅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泌霍。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡货抄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朱转,到底是詐尸還是另有隱情蟹地,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布藤为,位于F島的核電站怪与,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缅疟。R本人自食惡果不足惜分别,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一遍愿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茎杂,春花似錦错览、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刽脖,卻和暖如春羞海,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曲管。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工却邓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人院水。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓腊徙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親檬某。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撬腾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359