ARouter源碼解讀
以前看優(yōu)秀的開源項目,看到了頁面路由框架ARouter,心想頁面路由是個啥東東,于是乎網(wǎng)上搜索查看,是阿里出品開源的,主要是關(guān)于頁面跳轉(zhuǎn)的解耦框架。一直想看看具體是怎么實現(xiàn)的,今有時間便來一探究竟。
傳統(tǒng)的頁面跳轉(zhuǎn)就是調(diào)用系統(tǒng)的startActivity,里面的參數(shù)Intent攜帶了要跳轉(zhuǎn)的信息,可以傳入要跳轉(zhuǎn)的activity信息或者action。如果是action則要在清單文件里面配置。但是ARouter的實現(xiàn)頁面跳轉(zhuǎn)則另辟蹊徑,傳入一個path路徑,官方示例代碼如下
ARouter.getInstance().build("/test/activity2") .navigation();
初看到這樣的代碼你會想,這是要跳轉(zhuǎn)到哪里去呢?既然只有一個路徑,那么某個activity必然會和這個路徑有關(guān)系.果然在示例代碼的Test2Activity上面有個注解,里面就有個這個參數(shù)。
基于以前注解的知識,作猜想,只要獲取Activity上面的注解參數(shù),再把該參數(shù)和該Activity綁定起來,全局緩存,只要匹配跳轉(zhuǎn)路徑是該Activity上注解參數(shù),就讓它跳轉(zhuǎn)到該Activity.大概應(yīng)該是這樣。有了這個猜想,再去查看源碼,看看是否符合猜想。下面開始探索源碼之旅。
首先調(diào)用其初始化方法
ARouter.init(getApplication());
點到這個ARouter類init方法里面,發(fā)現(xiàn)里面調(diào)用的是一個_ARouter的初始化方法,再初看其他方法,發(fā)現(xiàn)所有方法其實都是調(diào)用的_ARouter類的方法。
看_ARouter類的初始化方法
protected static synchronized boolean init(Application application) {????
mContext = application;???
LogisticsCenter.init(mContext, executor);//LogisticsCenter類的初始化????
logger.info(Consts.TAG, "ARouter init success!");//日志初始化???
?hasInit = true;//根據(jù)該變量判斷是否初始化了,若使用前未調(diào)用初始化方法,則拋出未初始化異常信息 ????// It's not a good idea.????//
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {????// ????application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());????
// }???
?return true;
}
其主要是LogisticsCenter類的初始化。
LogisticsCenter類的初始化主要都做了什么事情呢?
繼續(xù)往下看
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {???...前面省略無關(guān)代碼
Set routerMap;????????// 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 generate by arouter-compiler.//獲取Arouter自動生成的類的信息???????????
?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();????????????}????????????
PackageUtils.updateVersion(context); ???// Save new version name when router map update finish.???????
?} 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()));????????}????????
logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");????????startInit = System.currentTimeMillis();
//將路徑按分類加入緩存???????
?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);????????????}????????}...后面省略無關(guān)代碼??????}
執(zhí)行完getFileNameByPackageName這個方法后,Set里面都有些什么呢?
緩存類
初始化完畢。
小結(jié):初始化方法只是要把緩存里面的groupsIndex和providerIndex和intercaptorsIndex三個Map集合先緩存了。groupsIndex主要是組名的路徑和對象class映射。什么是組名的路徑呢?
比如當(dāng)前示例傳入的路徑為/test/activity2,則組名就是test。providerIndex主要是用于依賴注入的path路徑和class對應(yīng)的關(guān)系。至于啥事依賴注入,就是聲明一個對象,并不實例化,由框架根據(jù)你傳入的參數(shù)生成對應(yīng)的對象,說白點就是通過框架實例化你所需要的對象(試想連對象都不用自己實例化了,是不是耦合性就非常低了)。如果知道java web spring 框架的話,那么這個就很清楚了,因為spring框架里面就大量應(yīng)用了依賴注入。InterceptorsIndex則是保存了攔截器的path和class對應(yīng)的關(guān)系,何為攔截器,攔截什么操作?,繼續(xù)看下面代碼。
現(xiàn)在查看調(diào)用代碼,
ARouter.getInstance()????????.build("/test/activity2")????????.navigation();
Build方法調(diào)用_ARouter方法的build方法
public Postcard build(String path) {????return _ARouter.getInstance().build(path);}
查看_ARouter方法
/**?* Build postcard by path and default group?*/
protected Postcard build(String path) {????
if (TextUtils.isEmpty(path)) {????????t
hrow new HandlerException(Consts.TAG + "Parameter is invalid!");???
?} else {????????
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);????????
if (null != pService) {????????????path = pService.forString(path);????????}????????
return build(path, extractGroup(path));????}}
其中extractGroup方法是根據(jù)路徑解析其路徑所在的組。
/**?* Extract the default group from path.?*/
private String extractGroup(String path) {????
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {????????throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");????
}????try {
//如果當(dāng)前路徑是/xxx/... 則默認(rèn)其在xxx組內(nèi). ????????
String defaultGroup = path.substring(1, path.indexOf("/", 1));???????
?if (TextUtils.isEmpty(defaultGroup)) {????????????
throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");????????
} else {????????????return defaultGroup;????????}????
} catch (Exception e) {???????
?logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());????????return null;????}}
最終調(diào)用這個方法
/**?* 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對象。
先查看Postcard對象的navigation方法,里面又多個重載方法,但最后都調(diào)用了此方法。
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {????
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);}
還是調(diào)用_ARouter的navigation方法
查看navigation方法
/**?* 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) {????try {????????LogisticsCenter.completion(postcard);???
?} catch (NoRouteFoundException ex) {????????logger.warning(Consts.TAG, ex.getMessage());
..//后面代碼暫時省略
/**?* Completion the postcard by route metas
?*?*@param postcard Incomplete postcard, should completion by this method.?*/
public synchronized static void completion(Postcard postcard) {????????
//從緩存中獲取path對應(yīng)的RouteMeta類型????//RouteMeta中保存了該路徑對應(yīng)的目標(biāo)的Class類型????RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());???
?//如果沒有,去加載????if (null == routeMeta) { ???
// Maybe its does't exist, or didn't load.????????//查找所在組群的類信息???????
?Class 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.???????????????
?IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
//加載該群組下面的路徑和class對應(yīng)的信息????????????????
iGroupInstance.loadInto(Warehouse.routes);????????????????
Warehouse.groupsIndex.remove(postcard.getGroup());????????????????????
completion(postcard); ??// Reload????????}????}
else {????????
postcard.setDestination(routeMeta.getDestination()); ???
//設(shè)置跳轉(zhuǎn)的class信息???????
?postcard.setType(routeMeta.getType()); ?????????????//設(shè)置路由類型????????postcard.setPriority(routeMeta.getPriority()); ?????//路由優(yōu)先級????????postcard.setExtra(routeMeta.getExtra());????????
Uri rawUri = postcard.getUri();???????
?if (null != rawUri) { ??// Try to set params into bundle.????????????
Map resultMap = TextUtils.splitQueryParameters(rawUri);????????????
Map paramsType = routeMeta.getParamsType();????????????if (MapUtils.isNotEmpty(paramsType)) {????????????????// Set value by its type, just for params which annotation by @Param????????????????for (Map.Entry 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());????????}...//如果是acitivty跳轉(zhuǎn),下面代碼省略暫時不看???????}
該方法主要是設(shè)置postcard對象里面的要跳轉(zhuǎn)的信息,和跳轉(zhuǎn)時如果有參數(shù)則設(shè)置參數(shù)。
那么如何使將path和對應(yīng)的class類信息加載進(jìn)來的呢?
通過調(diào)試可以發(fā)現(xiàn)是調(diào)用了ARouter$$Group$$test的loadInto方法
找到該類的該方法,loadInto方法里面有設(shè)置路徑和對應(yīng)的RoteMeta對象一一對應(yīng),而RoteMeta對象則是保存了相應(yīng)的屬性,比如類型,和跳轉(zhuǎn)的目標(biāo)類類型。該類沒有在src目錄下,而是在build目標(biāo)下,由此可見該類是自動編譯的。
但是是如何自動編譯的呢,這個問題暫時放下。
繼續(xù)往下看,既然已經(jīng)找到了path和要跳轉(zhuǎn)的activity class的信息了,那么接下應(yīng)該就是調(diào)用真正的跳轉(zhuǎn)方法了。繼續(xù)navigation方法往下看.
/**?* 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) {
//省略前面代碼...
//如果設(shè)置了greenChannel為true,表示不需要攔截,為false。則跳轉(zhuǎn)前還需做一步攔截操作????????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);????????????}??????????????????});??
?} else {???????
return _navigation(context, postcard, requestCode, callback);??
?}????return null;}
最后調(diào)用了_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());//設(shè)置要跳轉(zhuǎn)攜帶的參數(shù)????????????// 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);????????????}????????????// Navigation in main looper.????????????
new Handler(Looper.getMainLooper()).post(new Runnable() {????????????????
@Override????????????????public void run() {???????????????????
?if (requestCode > 0) { ?// Need start for result????????????????????????
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());???????????????????
?} else {
//真正的跳轉(zhuǎn)動作????????????????????????
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());????????????????????}???????????????????????????????});????????????
break;???????//如果類型是acitivity則,后面的可以省略不看????return null;}
終于看到了startActivity方法了。
查看跳轉(zhuǎn)到Test1Activity的示例代碼時,里面攜帶了要傳入Test1Activity的參數(shù),但是Test1Activity里面沒有看到獲取參數(shù)代碼,而是每個參數(shù)上面還有@Autowited注解
@Route(path = "/test/activity1")public class Test1Activity extends AppCompatActivity {????@Autowired????String name = "jack";
還有onCreate方法里面的調(diào)用了inject方法.由此可猜測該方法應(yīng)該已經(jīng)把@Autowited下面的變量全都賦值了。
ARouter.getInstance().inject(this);
追蹤查看該方法最終調(diào)用了_ARouter里面的inject方法
static void inject(Object thiz) {????
AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());????
if (null != autowiredService) {????????autowiredService.autowire(thiz);????}}
AutowiredService 是個接口,其實現(xiàn)是AutowiredServiceImpl類
查看AutowiredServiceImpl類的autowite方法
@Overridepublic void autowire(Object instance) {????
String className = instance.getClass().getName();????
try {????????if (!blackList.contains(className)) {
//????????????ISyringe autowiredHelper = classCache.get(className);????????????
if (null == autowiredHelper) { ?// No cache.
//查找對應(yīng)的ISyringe 實現(xiàn)類,className$$ARouter$$Autowired
????????????????autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();????????????}????????????
autowiredHelper.inject(instance);????????????
classCache.put(className, autowiredHelper);????????}????}
catch (Exception ex) {????????
blackList.add(className); ???// This instance need not autowired.????}}
最終調(diào)用了className$$ARouter$$Autowired類里面的inject
方法,看到這個類的類名字,在該工程查找該類,發(fā)現(xiàn)也在build目錄下
可以看到最終這里調(diào)用getIntent().getXXX獲取Intent攜帶的參數(shù)。因為幾乎所有獲取攜帶參數(shù)的代碼方式是一樣的,因此這里直接也是用自動編譯而成的。
小結(jié):梳理下跳轉(zhuǎn)界面的步驟 1 首先根據(jù)路徑生成一個路徑和組名的Postcard對象。2查看緩存里面是否有組名的class信息,如果沒有,根據(jù)組名獲取所有組名下面的的class信息。3 根據(jù)獲取的信息填充postcard對象。4 如果需要攔截,如執(zhí)行攔截器的方法。5 如果沒有攔截,執(zhí)行跳轉(zhuǎn)動作. 6 跳轉(zhuǎn)的那個activity執(zhí)行注入?yún)?shù)方法。
總結(jié):ARouter的界面跳轉(zhuǎn)流程分析完畢。但是ARouter的用處不止界面跳轉(zhuǎn),還有依賴注入等其他功能,根據(jù)github官網(wǎng)介紹
1. 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn),并自動注入?yún)?shù)到目標(biāo)頁面中
2. 支持多模塊工程使用
3. 支持添加多個攔截器,自定義攔截順序
4. 支持依賴注入,可單獨作為依賴注入框架使用
5. 支持InstantRun
6.支持MultiDex(Google方案)
7.映射關(guān)系按組分類、多級管理,按需初始化
8. 支持用戶指定全局降級與局部降級策略
9. 頁面、攔截器、服務(wù)等組件均自動注冊到框架
10. 支持多種方式配置轉(zhuǎn)場動畫
11. 支持獲取Fragment
12. 完全支持Kotlin以及混編(配置見文末 其他#5)
有興趣可以自行查看其他功能詳解
ARouter github地址https://github.com/alibaba/ARouter