ARouter
ARouter 是阿里云出品的Android中間件木羹,負(fù)責(zé)處理盅跳轉(zhuǎn)頁面時(shí)的邏輯,簡(jiǎn)化和優(yōu)化之前跳轉(zhuǎn)頁面的方法昨稼。同時(shí)他也是組件化的基礎(chǔ)之一步咪,實(shí)現(xiàn)了模塊間的解耦论皆。
ARouter使用
項(xiàng)目的主頁有提供ARouter的使用方法,主要就是
- 注解可以跳轉(zhuǎn)的類(Activity,Service点晴,ContentProvider感凤,F(xiàn)ragment等)
- 要跳轉(zhuǎn)頁面的時(shí)候使用Arouter,類似于
ARouter.getInstance().build("/kotlin/test").withString("name", "老王").withInt("age", 23).navigation();
用法是不能再簡(jiǎn)單了粒督,猜想是把一個(gè)String
與一個(gè)Activity
對(duì)應(yīng)起來就可以了陪竿,然而實(shí)際代碼應(yīng)該比猜想復(fù)雜N多倍。下面一起分析一下這個(gè)中間屠橄,挖掘他的所有信息族跛。
ARouter源碼解析
下面分析源碼分為幾部分
- 跳轉(zhuǎn)頁面流程分析
- 在類上和成員上的注解都做了什么
- 解釋在ARouter文檔上的所有功能特點(diǎn)和典型應(yīng)用是如何實(shí)現(xiàn)的,把這些特點(diǎn)抄到下面來锐墙,方便查看礁哄,我們會(huì)挨個(gè)把所有的特點(diǎ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è))
二烁竭、典型應(yīng)用
- 從外部URL映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn)吉挣,模塊間解耦
- 攔截跳轉(zhuǎn)過程颖变,處理登陸、埋點(diǎn)等邏輯
- 跨模塊API調(diào)用听想,通過控制反轉(zhuǎn)來做組件解耦
跳轉(zhuǎn)頁面流程分析
使用的方法是
ARouter.getInstance()
.build("/kotlin/test")
.withString("name", "老王")
.withInt("age", 23)
.navigation();
ARouter使用了單例,內(nèi)部存儲(chǔ)了頁面的映射表马胧,初始化狀態(tài)汉买,debug信息等,等一下都會(huì)用到的信息(其實(shí)在保存在_ARouter單例中)佩脊,同時(shí)也方便在使用的時(shí)候直接使用ARouter的靜態(tài)方法蛙粘。
實(shí)際上除了ARouter之外還有一個(gè)_ARouter類,ARouter中幾乎是把所有的方法都直接給了_ARouter處理威彰,這一層的轉(zhuǎn)換把_ARouter中復(fù)雜的方法轉(zhuǎn)化為ARouter中簡(jiǎn)單的方法向外暴露出牧,算是一種門面吧,值得學(xué)習(xí)一下歇盼。
build方法不例外地轉(zhuǎn)給了_ARouter
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
在_ARouter中構(gòu)造了PostCard
/**
* 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 {
//這個(gè)Service里可以將傳入的path處理舔痕,換在另外一個(gè),也相當(dāng)于一個(gè)攔截器
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
其中PathReplaceService相關(guān)處理邏輯是根據(jù)當(dāng)前的path換成另外的path繼續(xù)走下面的流程,所以主流程還是build方法伯复,其中有有個(gè)extractGroup(path)方法調(diào)用慨代,將path中第一部分抽取出來作為group,繼續(xù)看主線
/**
* 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);
}
}
這里又找了一次PathReplaceService啸如,出于什么目的侍匙,很簡(jiǎn)單,因?yàn)檫@個(gè)方法有可能不是上面那個(gè)路徑調(diào)過來的叮雳,_ARouter還有兩個(gè)參數(shù)的builde方法想暗,直接調(diào)用了這個(gè)方法,但是不做任何區(qū)分直接再找一次也有點(diǎn)可以優(yōu)化的空間??
返回的結(jié)果就是最后構(gòu)造出來的一個(gè)PostCard帘不,這個(gè)類上的解釋是A container that contains the roadmap.
说莫,包含這一次路由過程中所要的所有信息。PostCard的構(gòu)造方法沒有什么邏輯厌均,就是另外構(gòu)造了一個(gè)bundle放在了PostCard里面?zhèn)湎旅媸褂谩?/p>
到這里就返回到了最初調(diào)用build的地方唬滑,再往下是兩個(gè)withXXX方法,就是向PostCard中放入幾個(gè)跳轉(zhuǎn)頁面要帶過去的信息棺弊,都是直接放到了bundle里面晶密,當(dāng)然這不是主線。
繼續(xù)看下面的navigation方法模她。
/**
* Navigation to the route with path in postcard.
* No param, will be use application context.
*/
public Object navigation() {
//后面會(huì)用application的Context
return navigation(null);
}
給出的例子都是使用沒有參數(shù)所navigation方法稻艰,后面拿application
的Context
,要設(shè)置intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
感覺有點(diǎn)誤導(dǎo)的感覺侈净,這里正正經(jīng)經(jīng)地傳個(gè)activity過來才應(yīng)該是最廣泛的使用方法尊勿。
/**
* Navigation to the route with path in postcard.
*
* @param context Activity and so on.
*/
public Object navigation(Context context) {
//這里沒有caback,這個(gè)callback有onFound畜侦,onLost元扔,onArrival,和onInterrupt方法旋膳,都是跳轉(zhuǎn)時(shí)的各種回調(diào)澎语,單獨(dú)的跳轉(zhuǎn)降級(jí)就是通過這個(gè)callback完成的
return navigation(context, null);
}
/**
* Navigation to the route with path in postcard.
*
* @param context Activity and so on.
*/
public Object navigation(Context context, NavigationCallback callback) {
//這里又增加了一個(gè)-1的參數(shù),不求不需求forResult
return ARouter.getInstance().navigation(context, this, -1, callback);
}
到這里方法調(diào)用就出了PostCard验懊,到了ARouter中擅羞,當(dāng)然又會(huì)委托給_ARouter進(jìn)行真正的業(yè)務(wù)。
看_ARouter的方法义图。
/**
* Use router navigation.
*/
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//主流程减俏,補(bǔ)全路由所要的信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast...
}
if (null != callback) {
callback.onLost(postcard);
} else { // No callback for this invoke, then we use the global degrade service.
//統(tǒng)一降級(jí)邏輯
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
//主流程,根據(jù)是否綠色通道走攔截的邏輯
//這里還有個(gè)友情提示: It must be run in async thread, maybe interceptor cost too mush time made ANR.
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
//走完攔截器并通過的碱工,繼續(xù)走跳轉(zhuǎn)的邏輯
_navigation(context, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
//主流程娃承,綠色通道的主要邏輯奏夫,此時(shí)所有的信息已經(jīng)準(zhǔn)備完成,下面就是與系統(tǒng)交互進(jìn)行跳轉(zhuǎn)了
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
主流程沒有幾名話草慧,先補(bǔ)全路由的信息桶蛔,走攔截邏輯,然后真正的跳轉(zhuǎn)漫谷,其中補(bǔ)全PostCard是重要過程仔雷,我們看一下方法詳情
/**
* Completion the postcard by route metas
* @param postcard Incomplete postcard, should complete by this method.
*/
public synchronized static void completion(Postcard postcard) {
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
//沒有找到RouteMeta,檢查他所有的組是否還沒有加載舔示,如果已經(jīng)加載碟婆,則異常,沒有加載去加載
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(...);
} else {
// Load route and cache it into memory, then delete from metas.
try {
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
//加載所在的組惕稻,如果加載完成就把這個(gè)group從未加載列表中刪除
iGroupInstance.loadInto(Warehouse.routes);
//將些組從未加載中移除竖共,防止重復(fù)加載,
Warehouse.groupsIndex.remove(postcard.getGroup());
} catch (Exception e) {
throw new HandlerException(...);
}
// 加載了組信息俺祠,重新去重新去走補(bǔ)全的邏輯
completion(postcard); // Reload
} else {
將RouteMeta的信息放到PostCard中公给,
如果是通過uri跳轉(zhuǎn)的話再將路徑中的信息解析出來放到postCard的bound中
下面又對(duì)兩種類型的跳轉(zhuǎn)做的特殊處理
1. PROVIDER
從倉庫中找到provider的實(shí)例,疳賦值給postCard
設(shè)置綠色通道蜘渣,防止攔截
2. FRAGMENT
設(shè)置綠色通道淌铐,防止攔截
}
}
此時(shí)post的信息已經(jīng)完全了,我們路過攔截的邏輯蔫缸,直接看下面真正的跳轉(zhuǎn)方法腿准,感覺是沒有必要把代碼再拿出來,就是根據(jù)類型區(qū)分了一下拾碌,activity就直接new Intent進(jìn)行跳轉(zhuǎn)吐葱,如果是Privider,F(xiàn)ragment就返回實(shí)例校翔。
到這里基本完成了一次跳轉(zhuǎn)頁面所走的全部路徑弟跑。并沒有高深難懂的邏輯,一個(gè)比較好玩的就是PathReplaceService防症,DegradeService窖认,SerializationService等都是通過注冊(cè)一個(gè)Service完成的,這就大大增加了這個(gè)框架的靈活性告希,而且框架向外提供的這個(gè)功能,自己內(nèi)部已經(jīng)先用起來了烧给,這個(gè)也是挺有意思的燕偶。
ARouter中的注解有什么用,是怎么起作用的
@Route
作用:注解一個(gè)類础嫡,這個(gè)類就可以通過ARouter找到使用
Route主要有兩個(gè)屬性指么,path和group酝惧,在RouteProcessor中處理這個(gè)注解,在注解處理的方法中會(huì)根據(jù)注解的類型創(chuàng)建上面使用過的RouteMeta
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta routeMeta = null;
if (types.isSubtype(tm, type_Activity)) { // Activity
logger.info(">>> Found activity route: " + tm.toString() + " <<<");
// Get all fields annotation by @Autowired
Map<String, Integer> paramsType = new HashMap<>();
for (Element field : element.getEnclosedElements()) {
if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
// It must be field, then it has annotation, but it not be provider.
Autowired paramConfig = field.getAnnotation(Autowired.class);
paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), typeUtils.typeExchange(field));
}
}
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else if (types.isSubtype(tm, iProvider)) { // IProvider
logger.info(">>> Found provider route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
} else if (types.isSubtype(tm, type_Service)) { // Service
logger.info(">>> Found service route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
} else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
} else {
throw new RuntimeException("ARouter::Compiler >>> Found unsupported class type, type = [" + types.toString() + "].");
}
categories(routeMeta);
// if (StringUtils.isEmpty(moduleName)) { // Hasn't generate the module name.
// moduleName = ModuleUtils.generateModuleName(element, logger);
// }
}
分別構(gòu)建出來RouteMeta伯诬,還構(gòu)建出來一個(gè)分組的信息晚唇,下面將這些信息構(gòu)建兩個(gè)java文件。類似于這樣
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("service", ARouter$$Group$$service.class);
routes.put("test", ARouter$$Group$$test.class);
}
}
rootInfo盗似,存放所有的組的信息哩陕,就是上面找不到RouteMeta的時(shí)候會(huì)從這里找到對(duì)應(yīng)的組,再找到組信息對(duì)應(yīng)的類赫舒,然后加載
還有一個(gè)組詳細(xì)信息的類悍及,類似開這樣
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
}
}
加載的時(shí)候把這些信息加載到map中,以后跳轉(zhuǎn)使用
@Interceptor
作用:設(shè)置全局跳轉(zhuǎn)的攔截器接癌,可以設(shè)置優(yōu)先級(jí)
處理注解基本和和@Route一樣心赶,得到類,得到屬性缺猛,javapoet寫出一個(gè)類似于這樣的類:
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(7, Test1Interceptor.class);
}
}
這個(gè)map是個(gè)特別的map缨叫,根據(jù)key的值自動(dòng)排序,如果key重復(fù)會(huì)異常荔燎,也也是這個(gè)攔截器可以按優(yōu)先級(jí)排序的原因
@Autowired
作用:自動(dòng)裝配耻姥,注解成員后,可以自動(dòng)從Intent中解出數(shù)據(jù)并賦值給變量
實(shí)現(xiàn)也很相似湖雹,找到被注解的成員咏闪,生成一個(gè)helper,在需要將intent的數(shù)據(jù)解出來的時(shí)候使用helper的inject方法摔吏,ARouter又使用了一個(gè)AutowiredService專門做這個(gè)事鸽嫂,只要將要注入的類傳過來就可以了
@Override
public void autowire(Object instance) {
String className = instance.getClass().getName();
try {
if (!blackList.contains(className)) {
// 只有一個(gè)inject方法
ISyringe autowiredHelper = classCache.get(className);
if (null == autowiredHelper) { // No cache.
autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
}
// autowiredHelper就是根據(jù)注解生成的特定helper
autowiredHelper.inject(instance);
classCache.put(className, autowiredHelper);
}
} catch (Exception ex) {
blackList.add(className); // This instance need not autowired.
}
}
javaPoet實(shí)在是有點(diǎn)煩瑣,真的不愿把他的代碼拿來征讲。有意的同學(xué)可以直接去arouter查看
解釋所有官方列舉的特點(diǎn)
- 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)据某,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
在Manifast頁面中注冊(cè)了兩個(gè)filter
<intent-filter>
<data
android:host="m.aliyun.com"
android:scheme="arouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
<!-- App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="m.aliyun.com"
android:scheme="http"/>
<data
android:host="m.aliyun.com"
android:scheme="https"/>
</intent-filter>
第一個(gè)是處理特定的協(xié)議,如果協(xié)議中有arouter诗箍,則會(huì)用這個(gè)Acitity處理
還有一個(gè)是處理applink的癣籽,在網(wǎng)頁上點(diǎn)擊特定連接,也是在這個(gè)Activity中處理
在這個(gè)Activity主要代碼就兩行:
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation(this, new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
finish();
}
});
這里使用的是一個(gè)URI滤祖,在構(gòu)造PostCard的時(shí)候會(huì)將URI后面掛的參數(shù)直接轉(zhuǎn)化到bound中去筷狼,也就解釋了第一個(gè)特征。
- 支持多模塊工程使用
各模塊只是依賴一個(gè)String匠童,編譯時(shí)會(huì)掃描整個(gè)所有的工程埂材,所以直接就支持多模塊的工程。但是有個(gè)問題就是別的頁面要跳轉(zhuǎn)的時(shí)候都要將字符串寫死進(jìn)去汤求,如果定義常量的話會(huì)出現(xiàn)多個(gè)模塊依賴一個(gè)常量類的情況俏险。
- 支持添加多個(gè)攔截器严拒,自定義攔截順序
如果設(shè)置了這個(gè)優(yōu)先級(jí)別,生成的java代碼中會(huì)將這個(gè)優(yōu)先級(jí)做為key竖独,放到傳過來的一個(gè)容器中裤唠,而這個(gè)窗口的定義在com.alibaba.android.arouter.core.Warehouse
中,是一個(gè)UniqueKeyTreeMap莹痢,保證key是唯一的种蘸,并且按key進(jìn)行排序
這里也就解釋了自定義攔截順序的特點(diǎn)
- 支持依賴注入,可單獨(dú)作為依賴注入框架使用
不知道講的是什么格二。劈彪。
navigation方法返回的是一個(gè)Object
- 支持InstantRun
- 支持MultiDex(Google方案)
這里看到源碼中找了一個(gè)所有的的dex文件,再從這些所有的dex中查找要找的router的類顶猜,應(yīng)該就是處理這個(gè)問題沧奴。等于沒有說。长窄。就簡(jiǎn)單看一下調(diào)用鏈吧
Arouter.init() -> LogisticsCenter.init(mContext, executor)
-> ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);//這里就是是指定的ali的包名滔吠,就是生成的那些類包名
-> ClassUtils.getSourcePaths(context);//從多dex下找類
-> ClassUtils.tryLoadInstantRunDexFile(applicationInfo)//instantRun
-> 找到所有生成的注冊(cè)router的類
- 映射關(guān)系按組分類、多級(jí)管理挠日,按需初始化
路由的注解中可以注冊(cè)一個(gè)group信息疮绷,如果不定義這個(gè)group信息,arouter會(huì)拿路徑中的第一段做為group嚣潜。處理注解的時(shí)候會(huì)生成兩組信息冬骚,第一是組信息,其中有所有g(shù)roup的信息懂算,每一組都會(huì)指向一個(gè)描述這個(gè)組中所有路徑的類只冻。
初始化時(shí)僅僅加載了組的信息,并沒有加載每一組內(nèi)的所有路由计技,使用路由時(shí)會(huì)先查找有沒有這個(gè)路由信息喜德,如果沒有的話就去加載這一組所有的路由。做到了按需初始化垮媒。
這個(gè)過程上面路由過程已經(jīng)用代碼分析過了舍悯。
- 支持用戶指定全局降級(jí)與局部降級(jí)策略
每一次使用路由時(shí)可以傳入一個(gè)callback,作為單次路由失敗的降級(jí)策略睡雇,其實(shí)也不僅僅是降級(jí)策略萌衬,callback提供了多個(gè)回調(diào)方法使用:
public interface NavigationCallback {
//找到路由
void onFound(Postcard postcard);
//沒有找到,降級(jí)吧
void onLost(Postcard postcard);
//向android發(fā)出了startActivity的請(qǐng)求
void onArrival(Postcard postcard);
//使用攔截器時(shí)
void onInterrupt(Postcard postcard);
}
也可以注冊(cè)一個(gè)IProvider它抱,用來處理所有的降級(jí)策略秕豫。
- 頁面、攔截器抗愁、服務(wù)等組件均自動(dòng)注冊(cè)到框架
使用注解馁蒂,編譯期處理,運(yùn)行時(shí)直接無反射運(yùn)行(多dex什么的還是要反射)
- 支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫
支持蜘腌,無特殊
- 支持獲取Fragment
navigate的時(shí)候支持返回一個(gè)fragment沫屡,只要注冊(cè)了路由的fragment,都可以通過路由來得到實(shí)例撮珠。
- 完全支持Kotlin以及混編(配置見文末 其他#5)
- 支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊(cè))