如果應(yīng)用項目要做模塊化, 必然繞不開為了模塊間跳轉(zhuǎn)解耦而做的處理, 我們都知道一般都會采用路由模式, 本篇主要解析阿里的開源路由框架ARouter
, 源碼地址可見Github,本篇分析版本為api1.3.1
初始化
感謝源碼自帶Demo, 我們直接從他的整個使用流程開始看起.忽略掉Log模式和Debug模式的開啟, 首先我們需要初始化ARouter
ARouter.init(getApplication());
它內(nèi)部實際調(diào)用的是LogisticsCenter.init(Context context, ThreadPoolExecutor tpe)
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.
// 通過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();
}
// 當(dāng)router map更新的時候保存新的版本號
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() + "]");
}
}
這里loadRouterMap()
方法就是將是否通過插件注冊的tag初始化為false
, 我們主要看正常流程下的注冊, 所以關(guān)注點在else
的代碼塊中.對于routerMap
從上下文我們可以看出, 他是APT
自動生成的類名集合.
在Debug
模式下, 當(dāng)每次更新路由緩存的版本的時候, 都會從指定包名com.alibaba.android.arouter.routes
收集所有的className, 如果不為空 則更新磁盤緩存.
而非Debug
模式下, 不做收集, 直接獲取磁盤緩存, 如果沒有則new一個空集合.
然后通過反射新建對應(yīng)類實例并調(diào)用load
方法.我們可以看下debug
模式下, APT
生成類文件下的load
函數(shù)具體執(zhí)行了什么
public class ARouter$$Group$$arouter implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", null, -1, -2147483648));
atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", null, -1, -2147483648));
}
}
public class ARouter$$Providers$$arouterapi implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", null, -1, -2147483648));
}
}
public class ARouter$$Root$$arouterapi implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("arouter", ARouter$$Group$$arouter.class);
}
}
可以看出所有loadInto()
方法執(zhí)行的都是將注解的信息通過Map
強引用做內(nèi)存緩存管理.這里Warehouse.groupsIndex
管理的是組的路由生成文件的內(nèi)存, Warehouse.interceptorsIndex
攔截器的索引管理, Warehouse.providersIndex
服務(wù)(不是四大組件, 而是實現(xiàn)了繼承IProvider
接口的對象)索引管理.
跳轉(zhuǎn)
我們先看下對應(yīng)的API
ARouter.getInstance()
.build("/test/activity2")
.navigation();
首先需要build
一個Postcard
對象, 他包含了我們傳遞的跳轉(zhuǎn)信息.
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 判斷是否重寫跳轉(zhuǎn)URL, 沒有就使用原來的path構(gòu)建Postcard
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
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.navigation()
做跳轉(zhuǎn)工作, 在這個方法內(nèi)首先會針對我們記載路由信息的postcard
通過調(diào)用LogisticsCenter.completion(postcard);
進行信息補充完善
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
// 通過path獲取路由基礎(chǔ)信息
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
// 如果沒有獲取到路由基礎(chǔ)信息
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
// 則重新通過反射進行加載
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 {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
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() + "]");
}
// 遞歸進行路由的完善工作
completion(postcard); // Reload
}
} else {
// 完善postcard信息
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);
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement IProvider
// 如果路由目標(biāo)是一個provider
// 需要找到目標(biāo)實例
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);
// provider不會觸發(fā)攔截器
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
// fragment不會觸發(fā)攔截器
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
然后再回來看下路由跳轉(zhuǎn)做的事情.
/**
* Use router navigation.
*
* @param context
* @param postcard 路由信息
* @param requestCode RequestCode, 默認(rèn)為-1
* @param callback 申明的跳轉(zhuǎn)回調(diào)
*/
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
// postcard信息完善
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
// 沒有找到對應(yīng)路由信息的回調(diào)通知, 如果沒有回調(diào), 會通過DegradeService服務(wù)進行通知, 但是要根據(jù)他發(fā)出的進行處理, 需要我們實現(xiàn)DegradeService的接口
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;
}
// 回調(diào)通知onFound
if (null != callback) {
callback.onFound(postcard);
}
// 當(dāng)需要處理攔截器內(nèi)容的時候
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
// 攔截器處理
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
// 繼續(xù)跳轉(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) {
// 回調(diào)
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
攔截器的處理是通過interceptorService
代理來實現(xiàn), 我們可以往里面看
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
private static boolean interceptorHasInit;
private static final Object interceptorInitLock = new Object();
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
checkInterceptorsInitStatus();
if (!interceptorHasInit) {
callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
return;
}
// 通過線程池進行異步管理執(zhí)行
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
// 通過interceptorCounter倒數(shù)計數(shù)器, 一個個執(zhí)行擁有的攔截器
_excute(0, interceptorCounter, postcard);
// 默認(rèn)timeout時間是0.3s
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
// 如果在postcard.getTimeout()時間內(nèi)沒有執(zhí)行完
if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings.
// 取消
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) { // Maybe some exception in the tag.
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
// 在沒有超時和拋出異常的情況下, 則繼續(xù)往后執(zhí)行
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
/**
* Excute interceptor
*
* @param index current interceptor index
* @param counter interceptor counter
* @param postcard routeMeta
*/
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
// 遞歸處理
_excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}
@Override
public void onInterrupt(Throwable exception) {
// Last interceptor excute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // save the exception message for backup.
counter.cancel();
// Be attention, maybe the thread in callback has been changed,
// then the catch block(L207) will be invalid.
// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
// if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread.
// throw new HandlerException(exception.getMessage());
// }
}
});
}
}
@Override
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehouse.interceptors.add(iInterceptor);
} catch (Exception ex) {
throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
}
}
interceptorHasInit = true;
logger.info(TAG, "ARouter interceptors init over.");
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
}
}
});
}
private static void checkInterceptorsInitStatus() {
synchronized (interceptorInitLock) {
while (!interceptorHasInit) {
try {
interceptorInitLock.wait(10 * 1000);
} catch (InterruptedException e) {
throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
}
}
}
}
}
ok, 在進行了攔截器處理(或者沒有攔截器的情況下), 我們走到了_navigation(context, postcard, requestCode, callback)
, 到這里基本又是熟悉的配方了, 可以看到他最后通過startActivity
來進行跳轉(zhuǎn).
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);
}
// 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 {
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);
}
}
});
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;
}