ARouter探究(一)
前言
- ARouter 是 Alibaba 開源的一款 Android 頁面路由框架,特別適用于模塊化開發過程中,頁面跳轉所帶來的耦合問題。由于自己才疏學淺,有理解不當之處還請指教。
-
簡介
別人有詳細的介紹,這就不寫了
技術準備
幾個問題
-
怎么支持多模塊的?
- 首先在編譯期怎么知道多個模塊怎么加載,我們看下ARouter怎么做到的?
- 既然是編譯期我們猜想肯定用到APT,在Apt技術中可以通過在buidGradle文件中拿到參數,然后交給Process進行處理。看下ARouter是不是這樣做的?
- build.gradle文件中配置
javaCompileOptions { annotationProcessorOptions { arguments = [ moduleName : project.getName() ] } }
- 我們可以在AbstractProcessor類中的init(ProcessingEnvironment processingEnv)方法里面獲取配置
Map<String, String> options = processingEnv.getOptions(); if (MapUtils.isNotEmpty(options)) { moduleName = options.get(KEY_MODULE_NAME); }
- build.gradle文件中配置
- 既然是編譯期我們猜想肯定用到APT,在Apt技術中可以通過在buidGradle文件中拿到參數,然后交給Process進行處理。看下ARouter是不是這樣做的?
- 首先在編譯期怎么知道多個模塊怎么加載,我們看下ARouter怎么做到的?
-
怎么加載模塊的數據的呢?
-
針對路由存在路由表,那么必然存在初始化的過程?
- ARouter.init(getApplication()); ARouter的初始化此過程是初始化路由表信息。我們看下做了啥?
我們發現他調用了
此處ARouter用了代理模式,實際使用了_Arouter的init方法。我們再進去看發現__ARouter.init(application)
初始化交給了LogisticsCenter這個類,它是處理路由跳轉的核心類。init方法里如下LogisticsCenter.init(mContext, executor);
final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
這是什么鬼?看到這里我們可能要蒙了,這是干什么的。因為我們前面說了init肯定要把路由信息加載到我們要存儲的地方。 ClassUtils.getFileNameByPackageName()方法,我們根據方法名可以判斷是遍歷包名下的文件拿到我們Clas名,這個包名就是我們apt生成文件所在的包下,我們先看幾個類APT生產的類目錄結構如下?for (String className : classFileNames) { 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); } }
image- 打開ARouter$$Root$$app
對于剛才LogisticsCenter.init方法里的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); } }
這個地方就是利用反射生成ARouter$$Root$$app對象,調用loadinto方法最后把內容傳給了Warehouse.groupsIndex。WareHouse是干啥的呢?看代碼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); }
class Warehouse { // Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); static Map<String, RouteMeta> routes = new HashMap<>(); // Cache provider static Map<Class, IProvider> providers = new HashMap<>(); static Map<String, RouteMeta> providersIndex = new HashMap<>(); // Cache interceptor static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]"); static List<IInterceptor> interceptors = new ArrayList<>(); static void clear() { routes.clear(); groupsIndex.clear(); providers.clear(); providersIndex.clear(); interceptors.clear(); interceptorsIndex.clear(); } }
- ARouter.init(getApplication()); ARouter的初始化此過程是初始化路由表信息。我們看下做了啥?
我們看到這里就是我們路由表緩存信息,交給對應的集合存儲,這里groupsIndex就存了我們的分組信息。我們看到這里初始化的時候只是把分組存進去了,但具體的路由信息并沒有加載進來,==這符合作者說的按組分類,按需加載提高查找效率,但是什么時候加載呢,我們先留個問號?后邊會講述==
-
-
路由怎么完成查找的的?首先看用例
ARouter.getInstance().build("/test/activity2").navigation();
- .build("/test/activity2")源碼如下
public Postcard build(String path) { return _ARouter.getInstance().build(path); }
==1==我們看到他還是交給了_ARouter進行處理,他的build方法如下:
protected Postcard build(String path) { if (TextUtils.isEmpty(path)) { throw 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)); } }
ARouter.getInstance().navigation(PathReplaceService.class);我們先看PathReplaceService是干啥的
public interface PathReplaceService extends IProvider { /** * For normal path. * * @param path raw path */ String forString(String path); /** * For uri type. * * @param uri raw uri */ Uri forUri(Uri uri); }
==2==其實就是預處理我們的path這是重定向用的。上面的navigation()方法肯定也是交給_ARouter進行處理的我們看下他的方法
protected <T> T navigation(Class<? extends T> service) { try { Postcard postcard = LogisticsCenter.buildProvider(service.getName()); // Compatible 1.0.5 compiler sdk. if (null == postcard) { // No service, or this service in old version. postcard = LogisticsCenter.buildProvider(service.getSimpleName()); } LogisticsCenter.completion(postcard); return (T) postcard.getProvider(); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); return null; } }
看到這里我們看到最后還是交給了我們的核心邏輯處理類LogisticsCenter處理Postcard postcard = LogisticsCenter.buildProvider(service.getName()); 此處是創建個Postcard,它是個一個container里面有我們整個跳轉所用到的所有信息包括跳轉動畫,看看buildProvider做了什么
public static Postcard buildProvider(String serviceName) { RouteMeta meta = Warehouse.providersIndex.get(serviceName); if (null == meta) { return null; } else { return new Postcard(meta.getPath(), meta.getGroup()); } }
首先他先從warehose里面取出我們重定向的path和group重新生成Postcard,如果沒有的話直接返回null,這里我們沒有,所以就返回了null然后是 LogisticsCenter.completion(postcard);看代碼
if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!"); }
如果為null直接拋出了異常,在哪里處理的呢,在黃字==2==的下面,然后catch異常返回了null,再回到黃字==1==處,我們走到了
build(path, extractGroup(path))
然后創建了PostCard.這就build完了
- ARouter.getInstance().build("/test/activity2").navigation();==3==然后就是navigation方法,他也是有_ARouter進行處理
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()); 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(); } 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) { callback.onFound(postcard); } 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) { _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 { return _navigation(context, postcard, requestCode, callback); } return null; }
還是這個類
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) { // 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.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 autoinject. postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{})); } // Save raw uri postcard.withString(ARouter.RAW_URI, rawUri.toString()); } }
這就是我們的重點所在,通過上面可知我們的postcard走到這里不會為null了但是RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());這個返回的是null的因為我們是第一次加載這個分組里的routeMeta,所以進入if語句 ==Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());查找我們PostCard里面帶的分組,然后是IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
這里就加載了我們的組內元素,也就是實現了上面所說的按需加載==,然后就completion(postcard);這時候重走方法,RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());routeMeta就有值了我們就找到了我們要的路由信息,也就是通過path找到了routeMeta.就有了我們要給PostCard附上我們的目的地信息,參數信息等。
然后這就走完了- 有人要問跳轉呢?我們回到黃字3處,return _navigation(context, postcard, requestCode, callback)最后走到這里,我們看下源碼
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 ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version. ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim()); } if (null != callback) { // Navigation over. callback.onArrival(postcard); } } }); }
在主線程中startActivity,終于找到我們的調整目的地。寫一篇會介紹依賴注入和攔截器。