對(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)行組件化改造的框架轩性,支持模塊間的路由、通信狠鸳、解耦
支持的功能
- 支持直接解析標(biāo)準(zhǔn) URL 進(jìn)行跳轉(zhuǎn)揣苏,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
- 支持多模塊工程使用
- 支持添加多個(gè)攔截器悯嗓,自定義攔截順序
- 支持依賴注入,可單獨(dú)作為依賴注入框架使用
- 支持 InstantRun
- 支持 MultiDex (Google 方案)
- 映射關(guān)系按組分類舒岸、多級(jí)管理绅作,按需初始化
- 支持用戶指定全局降級(jí)與局部降級(jí)策略
- 頁面芦圾、攔截器蛾派、服務(wù)等組件均自動(dòng)注冊(cè)到框架
- 支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫
- 支持獲取 Fragment
- 完全支持 Kotlin 以及混編(配置見文末 其他#5)
- 支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊(cè))
- 支持生成路由文檔
- 提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標(biāo)類
- 支持增量編譯(開啟文檔生成后無法增量編譯)
典型應(yīng)用
- 從外部 URL 映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn)个少,模塊間解耦
- 攔截跳轉(zhuǎn)過程洪乍,處理登陸、埋點(diǎn)等邏輯
- 跨模塊 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)思路就是:
- 開發(fā)者自己維護(hù)特定 path 和特定的目標(biāo)類之間的對(duì)應(yīng)業(yè)務(wù)關(guān)系,ARouter 只要求開發(fā)者使用包含了 path 的
@Route
注解修飾目標(biāo)類 - ARouter 在編譯階段通過注解處理器來自動(dòng)生成 path 和特定的目標(biāo)類之間的對(duì)應(yīng)關(guān)系永丝,即將 path 作為 key锹漱,將目標(biāo)類的 Class 對(duì)象作為 value 之一存到 Map 之中
- 在運(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)的路由信息表杆兵,其主要邏輯是:
- 如果當(dāng)前開啟了 debug 模式或者通過本地 SP 緩存判斷出 app 的版本前后發(fā)生了變化雁仲,那么就重新獲取全局路由信息,否則就還是使用之前緩存到 SP 中的數(shù)據(jù)
- 獲取全局路由信息是一個(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ì)重新獲取路由信息
- 獲取到的路由信息中包含了在
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)到 group
為 account
的頁面時(shí),就會(huì)再來反射調(diào)用 ARouter$$Group$$account
的 loadInto
方法傍念,即按需加載矫夷,等到需要用到的時(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)用到 _ARouter
的 build()
方法驳概,最終返回一個(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)到 group
為 account
的頁面時(shí)棋恼,就會(huì)再來反射調(diào)用 ARouter$$Group$$account
的 loadInto
方法返弹,即按需加載,等到需要的時(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);
}
}
···
}
然后邑遏,在 _ARouter
的 navigation
方法中,如何判斷到此次路由請(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
的主要邏輯是:
- 在第一次獲取
InterceptorServiceImpl
實(shí)例的時(shí)候,其init
方法會(huì)馬上被調(diào)用俩檬,該方法內(nèi)部會(huì)交由線程池來執(zhí)行萎胰,通過反射生成每個(gè)攔截器對(duì)象,并調(diào)用每個(gè)攔截器的init
方法來完成攔截器的初始化棚辽,并將每個(gè)攔截器對(duì)象都存到Warehouse.interceptors
中技竟。如果初始化完成了,則喚醒等待在interceptorInitLock
上的線程 - 當(dāng)攔截器邏輯被觸發(fā)屈藐,即
doInterceptions
方法被調(diào)用時(shí)榔组,如果此時(shí)第一個(gè)步驟還未執(zhí)行完的話,則會(huì)通過checkInterceptorsInitStatus()
方法等待第一個(gè)步驟執(zhí)行完成联逻。如果十秒內(nèi)都未完成的話搓扯,則走失敗流程直接返回 - 在線程池中遍歷攔截器列表,如果有某個(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è)元素:
- 包名
- 導(dǎo)包
- 注釋
- 實(shí)現(xiàn)類及繼承的接口
- 包含的方法及方法參數(shù)
- 方法體
- 修飾符
如果通過硬編碼的形式,即通過拼接字符串的方式來生成以上代碼也是可以的黎泣,但是這樣會(huì)使得代碼不好維護(hù)且可讀性很低恕刘,所以 ARouter 是通過 JavaPoet
這個(gè)開源庫來生成代碼的。JavaPoet
是 square 公司開源的 Java 代碼生成框架聘裁,可以很方便地通過其提供的 API 來生成指定格式(修飾符雪营、返回值、參數(shù)衡便、函數(shù)體等)的代碼
攔截器對(duì)應(yīng)的 AbstractProcessor
子類就是 InterceptorProcessor
献起,其主要邏輯是:
- 在 process 方法中通過 RoundEnvironment 拿到所有使用了 @Interceptor 注解進(jìn)行修飾的代碼元素 elements洋访,然后遍歷所有 item
- 判斷每個(gè) item 是否繼承了 IInterceptor 接口,是的話則說明該 item 就是我們要找的攔截器實(shí)現(xiàn)類
- 獲取每個(gè) item 包含的 @Interceptor 注解對(duì)象谴餐,根據(jù)我們?yōu)橹O(shè)定的優(yōu)先級(jí) priority姻政,將每個(gè) item 按順序存到 interceptors 中
- 如果存在兩個(gè)攔截器的優(yōu)先級(jí)相同,那么就拋出異常
- 將所有攔截器按順序存入 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 ????