您好,登錄后才能下訂單哦!
一、路由方案
原生的路由方案缺點:
顯式:直接的類依賴,耦合嚴重
隱式:規則集中式管理,協作困難
Manifest擴展性較差
跳轉過程無法控制
失敗無法降級
ARouter的優勢:
使用注解,實現了映射關系自動注冊 與 分布式路由管理
編譯期間處理注解,并生成映射文件,沒有使用反射,不影響運行時性能
映射關系按組分類、多級管理,按需初始化
靈活的降級策略,每次跳轉都會回調跳轉結果,避免StartActivity()一旦失敗將會拋出運營級異常
自定義攔截器,自定義攔截順序,可以對路由進行攔截,比如登錄判斷和埋點處理
支持依賴注入,可單獨作為依賴注入框架使用,從而實現 跨模塊API調用
支持直接解析標準URL進行跳轉,并自動注入參數到目標頁面中
支持獲取Fragment
支持多模塊使用,支持組件化開發
…….
這么多好處,是時候來了解一下 ARouter
了。
二、ARouter框架
上圖是根據 ARouter
一次基本的路由導航過程,整理的基本框架圖,涉及到主要流程,下面進行詳細介紹。
三、路由管理
1.注冊
通過注解,在編譯時收集使用了注解的類或變量并經過Android Process Tool處理進行統一管理。
包含三種注解@Autowired,@Interceptor,@Route。
@Route
注解定義
String path();//路徑URL字符串 String group() default "";//組名,默認為一級路徑名;一旦被設置,跳轉時必須賦值 String name() default "undefined";//該路徑的名稱,用于產生JavaDoc int extras() default Integer.MIN_VALUE;//額外配置的開關信息;譬如某些頁面是否需要網絡校驗、登錄校驗等 int priority() default -1;//該路徑的優先級
實現 @Route 注解
BlankFragment @Route(path = "/test/fragment") Test1Activity @Route(path = "/test/activity1")
該注解主要用于描述路由中的路徑URL信息,使用該注解標注的類將被自動添加至路由表中。
@Autowired
注解定義
boolean required() default false; String desc() default "No desc.";
實現 @Autowired 注解
@Autowired int age = 10; @Autowired HelloService helloService;
該注解是在頁面跳轉時參數傳遞用的。目標Class中使用該注解標志的變量,會在頁面被路由打開的時候,在調用 inject()
后自動賦予傳遞的參數值。
@Interceptor
注解定義
int priority();//該攔截器的優先級 String name() default "Default";//該攔截器的名稱,用于產生JavaDoc
實現 @Interceptor 注解
一般應用于IInterceptor的實現類,是路由跳轉過程中的攔截器,不分module,應用全局。
@Interceptor(priority = 7) public class Test1Interceptor implements IInterceptor { @Override public void process(final Postcard postcard, final InterceptorCallback callback) { ............ } }
2.收集
在編譯期間自動生成映射文件,arouter-compiler實現了一些注解處理器,目標在于生成映射文件與輔助文件。
三種類型的注解處理器,都實現了 AbstractProcessor
,主要功能如下:
首先通過注解處理器掃出被標注的類文件
按照不同種類的源文件進行分類
按照固定的命名格式生成映射文件
這樣就可以在運行期初始化的時候通過固定的包名來加載映射文件。
關于注解處理的源碼詳解見 阿里路由框架--ARouter 源碼解析之Compiler 。
以官方demo為例,通過注解處理器,按照固定的命名格式生成映射文件。
具體以 ARouter$$Root$$app
為例,看下注解處理器生成的類文件的內容:
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); } }
通過調用 loadInto()
方法將其管理的 group
類文件加載到集合中,方便后續路由查找。
3.加載
前面的收集都是在編譯器處理獲得的,那么加載就是到了運行期。 ARouter
為了避免內存和性能損耗,提出了“分組管理,按需加載”的方式。在前面的編譯處理的過程中,已經按照不同種類生成對應的映射文件。
以官方demo為示例,一個app模塊有一個Root結點,管理各個Group分組,每個Group分組下有著多個界面;此外app模塊下還有著Interceptor結點,以及provider結點。
其中Interceptor結點對應于自定義的攔截器,provider結點對應于IOC,以實現跨模塊API調用。
ARouter
在初始化的時候只會一次性地加載所有的root結點,而不會加載任何一個Group結點,這樣就會極大地降低初始化時加載結點的數量。當某一個分組下的某一個頁面第一次被訪問的時候,整個分組的全部頁面都會被加載進去。
初始加載
ARouter
其實是一個代理類,它的所有函數實現都交給 _ARouter
去實現,兩個都是單例模式。
public static void init(Application application) {//靜態函數進行初始化,不依賴對象 if (!hasInit) { logger = _ARouter.logger; //持有 日志打印的 全局靜態標量 _ARouter.logger.info(Consts.TAG, "ARouter init start.");//打印 ARouter初始化日志 hasInit = _ARouter.init(application);//移交 _ARouter去 初始化 if (hasInit) { _ARouter.afterInit(); } _ARouter.logger.info(Consts.TAG, "ARouter init over.");//打印 ARouter初始化日志 } }
繼續看一下 _ARouter
的初始化方法
protected static synchronized boolean init(Application application) { mContext = application;// 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; //靜態持有Application的上下文 executor = tpe;//靜態持有 線城池 try { // These class was generate by arouter-compiler. // 通過指定包名com.alibaba.android.arouter.routes,找到所有 編譯期產生的routes目錄下的類名(不包含裝載類) List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); for (String className : classFileNames) {//組別列表com.alibaba.android.arouter.routes.ARouter\$\$Root 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)) {//模塊內的攔截器列表com.alibaba.android.arouter.routes.ARouter\$\$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)) {//IOC的動作路由列表com.alibaba.android.arouter.routes.ARouter\$\$Providers // Load providerIndex ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } 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() + "]"); } }
通過上述代碼,實現了“分組管理,按需加載”的方式,加載了對應的三個注解處理器生成的類中管理的結點到路由集合中。
其中內存倉庫 Warehouse
緩存了全局應用的組別的清單列表、IOC的動作路由清單列表、模塊內的攔截器清單列表,3個map對象。
class Warehouse { // Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();//組別的列表 包含了組名與對應組內的路由清單列表Class的映射關系 static Map<String, RouteMeta> routes = new HashMap<>();//組內的路由列表 包含了對應分組下的,路由URL與目標對象Class的映射關系 // Cache provider static Map<Class, IProvider> providers = new HashMap<>(); //緩存IOC 目標class與已經創建了的對象 static Map<String, RouteMeta> providersIndex = new HashMap<>();//IOC 的動作路由列表包含了使用依賴注入方式的某class的 路由URL 與class映射關系 // 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<>();//已排序的攔截器實例對象 }
四、路由查找
ARouter.getInstance().build("/test/activity2").navigation();</pre>
以上述例子為例,看一下 ARouter
路由查找的過程。首先看一下 build
過程
1. build()
public Postcard build(String path) { return _ARouter.getInstance().build(path); } 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的build()并構建和返回PostCard對象。 一個Postcard對象就對應了一次路由請求,作用于本次路由全過程。
這部分代碼主要包含兩個部分:
首先來看一下PathReplaceService.class接口:
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); }
主要包含forString()和forUri兩個方法,針對路徑進行預處理,實現 “運行期動態修改路由”。
接下下,繼續通過build(path, extractGroup(path))進行路由導航,其中extractGroup()是從路徑中獲取默認的分組信息。
然后build()方法會返回一個Postcard對象,并把對應的路徑和分組信息傳入該對象。
分析完上面的過程,下面來詳細看下PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);中的navigation()方法,該方法實際調用了代理類_ARouter的navigation(Class<? extends T> service)方法。
2. navigation(Class<? extends T> service)
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.buildProvider(service.getName())
根據 Warehouse
保存的 providersIndex
的信息查找并構建返回一個 PostCard
對象
然后執行 LogisticsCenter.completion(postcard)
,該方法會根據 Warehouse
保存的 routes
的路由信息完善postcard對象,該方法在下面還會出現,到時候具體介紹
再回到上文介紹 ARouter.getInstance().build("/test/activity2").navigation()
,返回 PostCard
對象后,開始調用對應的 navigation()
方法。
3. navigation()
觀察 PostCard
中的該方法
public Object navigation() { return navigation(null); } public Object navigation(Context context) { return navigation(context, null); } public Object navigation(Context context, NavigationCallback callback) { return ARouter.getInstance().navigation(context, this, -1, callback); } public void navigation(Activity mContext, int requestCode) { navigation(mContext, requestCode, null); } public void navigation(Activity mContext, int requestCode, NavigationCallback callback) { ARouter.getInstance().navigation(mContext, this, requestCode, callback); }
最終調用了 ARouter
中的 navigation()
方法,在其中其實是調用了 _ARouter
中的 navigation()
方法。
該方法包含查找回調的調用、降級處理、攔截器處理具體路由操作。
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; }
其中最重要的兩個方法就是 LogisticsCenter.completion()
和 _navigation()
,下面詳細介紹。
public synchronized static void completion(Postcard postcard) { if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!"); } //根據路徑URL獲取到路徑元信息 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 { //將該組的組內清單列表加入到內存倉庫中,并把組別移除 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); // 再次觸發完善邏輯 } } else { postcard.setDestination(routeMeta.getDestination());//目標 class 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); 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); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } }
該方法就是完善 PostCard
,來實現一次路由導航。
接下來介紹另一個方法 _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://如果是Acitvity,則實現Intent跳轉 // 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://如果是IOC,則返回目標對象實例 return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT://如果是Fragment,則返回實例,并填充bundle 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; }
至此我們就完成了一次路由跳轉。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。