ARouter源碼分析

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容