ARouter主要是用于組件化開發(fā)中的組件之間的通信娱仔。
從ARouter的用法透析組件通信原理
// 需要接收消息的組件,要注冊Router注解
@Route(group = "app", path = "/app/Component")
public class ComponentActivity extends AppCompatActivity {
// 通過Router直接發(fā)送事件
// 構(gòu)造參數(shù)
Bundle bundle = new Bundle();
bundle.putString("data", "data from other module");
// 發(fā)送事件
ARouter.getInstance()
.build("/app/Component") // 跳轉(zhuǎn)頁面路由地址
.with(bundle) // 傳參
.navigation();
ARouter的實(shí)現(xiàn)原理
在代碼里加入的@Route注解腥刹,會在編譯時(shí)期通過apt生成一些存儲path和activity.class映射關(guān)系的類文件混移,然后app進(jìn)程啟動的時(shí)候會加載這些類文件桃笙,把保存這些映射關(guān)系的數(shù)據(jù)讀到內(nèi)存里(保存在map里),然后在進(jìn)行路由跳轉(zhuǎn)的時(shí)候腐缤,通過build()方法傳入要到達(dá)頁面的路由地址,ARouter會通過它自己存儲的路由表找到路由地址對應(yīng)的Activity.class(activity.class = map.get(path))肛响,然后new Intent(context, activity.Class)岭粤,當(dāng)調(diào)用ARouter的withString()方法它的內(nèi)部會調(diào)用intent.putExtra(String name, String value),調(diào)用navigation()方法特笋,它的內(nèi)部會調(diào)用startActivity(intent)進(jìn)行跳轉(zhuǎn)剃浇,這樣便可以實(shí)現(xiàn)兩個(gè)相互沒有依賴的module順利的啟動對方的Activity了。
1.ARouter.init(this);的執(zhí)行過程
init()方法源碼如下:
# ARouter.java
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
其中調(diào)用了_ARouter的init方法,最終調(diào)用到LogisticsCenter的init()
# _ARouter
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
#LogisticsCenter
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;
// 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.
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 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) {
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);
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
在該方法中只做了一件事猎物,導(dǎo)入生成的路由表虎囚。
ARouter.getInstance().build(path)
在build的時(shí)候,傳入要跳轉(zhuǎn)的路由地址蔫磨,build()方法會返回一個(gè)Postcard對象淘讥,我們稱之為跳卡。Postcard里面保存著跳轉(zhuǎn)的信息堤如。
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;
}
navigation
Postcard 的 navigation() 方法又會調(diào)用到 _ARouter 的以下方法來完成 Activity 的跳轉(zhuǎn)蒲列。
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 對象
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;
}
}
復(fù)制代碼
navigation 方法的重點(diǎn)在于 LogisticsCenter.completion(postcard) 這一句代碼拭宁。它反射調(diào)用 ARouteraccount 的 loadInto 方法,獲取詳細(xì)的路由對應(yīng)信息瓣俯。
completion 方法就是用來獲取詳細(xì)的路由對應(yīng)信息的杰标。該方法會通過 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 對應(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()));
}
//會執(zhí)行到這里掸犬,說明此 group 還未加載過,那么就來反射加載 group 對應(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)的代碼
···
}
}
復(fù)制代碼
回到_ARouter的navigation()方法來湾碎,可以清楚的看到最終人仍然是通過Intent(context,MainActivity.class)
的方式來實(shí)現(xiàn)跳轉(zhuǎn)的。