一遮咖、什么是路由?
路由是指路由器從一個(gè)接口上收到數(shù)據(jù)包造虏,根據(jù)數(shù)據(jù)路由包的目的地址進(jìn)行定向并轉(zhuǎn)發(fā)到另一個(gè)接口的過(guò)程御吞。—百度百科
以Android為例漓藕,各個(gè)組件module可以看成一個(gè)個(gè)網(wǎng)絡(luò)陶珠,而路由就可以看成是各個(gè)模塊頁(yè)面跳轉(zhuǎn)的中轉(zhuǎn)站,除了中轉(zhuǎn)以外享钞,路由還可以過(guò)濾或攔截一些不安全或不規(guī)范的跳轉(zhuǎn)揍诽。
二、為什么用阿里ARouter
官方定義:A framework for assisting in the renovation of Android componentization (幫助 Android App 進(jìn)行組件化改造的路由框架)
其實(shí)Android的跳轉(zhuǎn)已經(jīng)提供了Intent,Intent不僅可以顯示跳轉(zhuǎn)栗竖,也可以隱式跳轉(zhuǎn)暑脆。那么ARouter為什么還會(huì)存在呢?讓我們對(duì)比一下原生跳轉(zhuǎn)和路由跳轉(zhuǎn):
1狐肢、原生顯示跳轉(zhuǎn)必須依賴(lài)于類(lèi)添吗,強(qiáng)耦合,而隱式跳轉(zhuǎn)需要都在AndroidManifest里集中注冊(cè)份名,協(xié)作麻煩碟联;而路由跳轉(zhuǎn)通過(guò)URL索引,低耦合僵腺,協(xié)作方便鲤孵;
2、原生在通過(guò)startActivity啟動(dòng)跳轉(zhuǎn)后就交由系統(tǒng)控制辰如,并不能做攔截普监;路由跳轉(zhuǎn)可以對(duì)跳轉(zhuǎn)做過(guò)濾或攔截。
通過(guò)對(duì)比琉兜,我們發(fā)現(xiàn)路由特別適合用來(lái)做跳轉(zhuǎn)(尤其是組件間的通信及跳轉(zhuǎn))凯正,下面就來(lái)看一下阿里ARouter的集成及使用吧~
三、ARouter概述
ARouter 是一個(gè)用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由呕童、通信漆际、解耦
適用于以下場(chǎng)景:
從外部URL映射到內(nèi)部頁(yè)面,以及參數(shù)傳遞與解析
跨模塊頁(yè)面跳轉(zhuǎn)夺饲,模塊間解耦
攔截跳轉(zhuǎn)過(guò)程奸汇,處理登陸、埋點(diǎn)等邏輯
跨模塊API調(diào)用往声,通過(guò)控制反轉(zhuǎn)來(lái)做組件解耦
主要包括這些功能:
支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)擂找,并自動(dòng)注入?yún)?shù)到目標(biāo)頁(yè)面中
支持多模塊工程使用
支持添加多個(gè)攔截器,自定義攔截順序
支持依賴(lài)注入浩销,可單獨(dú)作為依賴(lài)注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射關(guān)系按組分類(lèi)贯涎、多級(jí)管理,按需初始化
支持用戶(hù)指定全局降級(jí)與局部降級(jí)策略
頁(yè)面慢洋、攔截器塘雳、服務(wù)等組件均自動(dòng)注冊(cè)到框架
支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
支持獲取Fragment
完全支持Kotlin以及混編(配置見(jiàn)文末 其他#5)
支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊(cè))
支持生成路由文檔
提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標(biāo)類(lèi)
ARouter使用
這里只介紹ARouter最基礎(chǔ)的用法–Activity跳轉(zhuǎn)陆盘,其他用法如通過(guò)URL跳轉(zhuǎn)、解析參數(shù)败明、聲明攔截器隘马、處理跳轉(zhuǎn)結(jié)果、聲明服務(wù)等妻顶,請(qǐng)?jiān)敿?xì)閱讀ARouter官網(wǎng)使用說(shuō)明
1酸员、添加依賴(lài)和配置
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
}
// 舊版本gradle插件(< 2.2)讳嘱,可以使用apt插件幔嗦,配置方法見(jiàn)文末'其他#4'
// Kotlin配置參考文末'其他#5'
2、添加注解
// 在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí)沥潭,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
3邀泉、初始化SDK
if (isDebug()) { // 這兩行必須寫(xiě)在init之前,否則這些配置在init過(guò)程中將無(wú)效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 開(kāi)啟調(diào)試模式(如果在InstantRun模式下運(yùn)行叛氨,必須開(kāi)啟調(diào)試模式呼渣!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化
4寞埠、發(fā)起路由操作
// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)(通過(guò)URL跳轉(zhuǎn)在'進(jìn)階用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳轉(zhuǎn)并攜帶參數(shù)
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
四屁置、Arouter核心思路
ARouter通過(guò)Apt技術(shù)(APT,就是Annotation Processing Tool 的簡(jiǎn)稱(chēng)仁连,就是可以在代碼編譯期間對(duì)注解進(jìn)行處理蓝角,并且生成Java文件,減少手動(dòng)的代碼輸入饭冬。)使鹅,生成保存路徑(路由path)和被注解(@Router)的組件類(lèi)的映射關(guān)系的類(lèi),利用這些保存了映射關(guān)系的類(lèi)昌抠,Arouter根據(jù)用戶(hù)的請(qǐng)求postcard(明信片)尋找到要跳轉(zhuǎn)的目標(biāo)地址(class),使用Intent跳轉(zhuǎn)患朱。
原理很簡(jiǎn)單,可以看出來(lái)炊苫,該框架的核心是利用apt生成的映射關(guān)系裁厅,這里要用到Apt技術(shù),讀者可以自行搜索了解一下侨艾。
分析
在自定義Application中進(jìn)行初始化:
// 盡可能早执虹,推薦在Application中初始化
ARouter.init(Application.this);
最終調(diào)用了LogisticsCenter.init(mContext, executor)
//LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
Set<String> routerMap;
//1、遍歷“com.alibaba.android.arouter.routes”路徑下的類(lèi)并把其加入到set中
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
// Save new version name when router map update finishes.
PackageUtils.updateVersion(context);
} 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>()));
}
//2唠梨、遍歷set袋励,將root、group、provider分類(lèi)并填充到Warehouse路由表中
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
((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);
}
}
}
}
LogisticsCenter.init方法比較長(zhǎng)茬故,上面只保留了核心代碼盖灸,我們來(lái)看上面這種加載方式,PackageUtils.isNewVersion(context)中判斷SharedPreferences(后面簡(jiǎn)稱(chēng)sp)里面是否有存儲(chǔ)versionName及versionCode均牢,如果沒(méi)有或者他們有更新的時(shí)候糠雨,需要重新加載一次com.alibaba.android.arouter.routes這個(gè)路徑下的類(lèi)名并填充到Set中,否則直接從sp中取數(shù)據(jù)并賦值到Set中去徘跪。接著就開(kāi)始遍歷這個(gè)Set,并通過(guò)Class.forName(className)這種反射方式去實(shí)例化類(lèi)并調(diào)用類(lèi)中的loadInto方法將注解對(duì)應(yīng)的索引信息添加到Warehouse路由表中。畫(huà)個(gè)圖來(lái)總結(jié)一下:
ARouter跳轉(zhuǎn)
ARouter跳轉(zhuǎn)時(shí)琅攘,直接使用ARouter.getInstance().build("xxx/xxx").navigation()即可完成跳轉(zhuǎn)垮庐,那我們就來(lái)看一下源碼,看看里面都做了什么坞琴,首先是build方法:
/**
* 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 = navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
/**
* Build postcard by uri
*/
protected Postcard build(Uri uri) {
if (null == uri || TextUtils.isEmpty(uri.toString())) {
throw new HandlerException(Consts.TAG + "Parameter invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
uri = pService.forUri(uri);
}
return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
}
}
/**
* 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);
}
}
三個(gè)方法中都有哨查,PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class),這個(gè)類(lèi)是用來(lái)預(yù)處理path和uri的剧辐,調(diào)用方需要實(shí)現(xiàn)PathReplaceService就可以做預(yù)處理寒亥,如果不實(shí)現(xiàn),默認(rèn)pService==null荧关,那么直接走下面的去初始化Postcard實(shí)體類(lèi)溉奕。
接著來(lái)看navigation方法,因?yàn)閎uild方法返回的是PostCard類(lèi)忍啤,所以調(diào)用的是PostCard類(lèi)的navigation方法加勤,經(jīng)過(guò)一系列跳轉(zhuǎn),最終來(lái)到_ARouter.getInstance().navigation(mContext, postcard, requestCode, callback) :
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
if (!postcard.isGreenChannel()) {
// It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
去除了部分無(wú)關(guān)代碼同波,只保留了核心代碼鳄梅,首先調(diào)用了LogisticsCenter.completion方法,我們追進(jìn)去看看:
//LogisticsCenter.java
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should complete by this method.
*/
public synchronized static void completion(Postcard postcard) {
// 從倉(cāng)庫(kù)的路由地址清單列表中拿到對(duì)應(yīng)的RouteMeta
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 根據(jù)一級(jí)地址未檩,拿到對(duì)應(yīng)的路由地址清單的文件類(lèi)(ARouter$$Root$$工程名)
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
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 {
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}
// Save params name which need auto inject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement IProvider
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
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());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
這個(gè)類(lèi)很長(zhǎng)戴尸,但是邏輯還是很清晰的:首先從Warehouse路由表的routes中獲取RouteMeta,但是第一次獲取的時(shí)候?yàn)榭眨ㄒ驗(yàn)閕nit時(shí)只填充了Warehouse路由表的groupsIndex冤狡、interceptorsIndex孙蒙、providersIndex,還記得嗎?)筒溃,接著從Warehouse.groupsIndex中根據(jù)group的名字找到對(duì)應(yīng)的group索引马篮,并將生成的索引類(lèi)的map數(shù)據(jù)加載到Warehouse.routes中,然后把Warehouse.groupsIndex中對(duì)應(yīng)的group刪除掉怜奖,以免重復(fù)加載數(shù)據(jù)浑测,然后調(diào)用了completion(postcard)進(jìn)行重新加載。此時(shí)Warehouse.routes已經(jīng)不為空,根據(jù)path獲取對(duì)應(yīng)的RouteMeta迁央,就會(huì)走到else邏輯中掷匠,先是對(duì)PostCard設(shè)置了一堆屬性,最后對(duì)IProvider的子類(lèi)進(jìn)行了初始化并加載到Warehouse.providers中岖圈,同時(shí)也設(shè)置到PostCard中讹语,并給PROVIDER和FRAGMENT設(shè)置了綠色通道(不會(huì)被攔截)》淇疲總結(jié)一下:主要邏輯就是通過(guò)Warehouse.groupsIndex找到對(duì)應(yīng)的group并進(jìn)行加載顽决,實(shí)現(xiàn)了分組加載路由表。
我們繼續(xù)回到navigation方法中往下走导匣,首先通過(guò)postcard.isGreenChannel()判斷是否會(huì)攔截才菠,如果攔截,就會(huì)走interceptorService的邏輯(interceptorService是在afeterInit中初始化的)贡定,否則就走到了_navigation邏輯中赋访,那么來(lái)看_navigation方法:
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
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// Navigation in main looper.
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) {
// Need start for result
if (currentContext instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
}
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
最終ARouter跳轉(zhuǎn)Activity最終也是用原生的Intent實(shí)現(xiàn)的,如果navigation()不傳入context缓待,則使用初始化時(shí)Application作為context蚓耽,如果是FRAGMENT、PROVIDER旋炒、CONTENT_PROVIDER步悠、BOARDCAST,通過(guò)反射方式初始化并返回即可国葬。
源碼分析之?dāng)r截處理
參考文檔
鏈接:路由框架ARouter使用及源碼解析
鏈接:ARouter源碼解析
鏈接:ARouter 官方文檔