為什么需要路由模塊
方便不同的模塊間相互跳轉,增加模塊間的獨立性,而顯式的 startActivity
方式顯然是做不到的,而且簡潔優雅,當然還有其他更多的優點
需要考慮實現的基本功能
1.
Activity
和Fragment
的跳轉2.跳轉間的傳參
3.跳轉
Activity
的launcherFlag
處理?4.轉場動畫?
開源庫分析
ActivityRouter
特點:編譯時注解、不支持 Fragment、多組件支持
實現
-
路由映射
映射關系通過
Router#map
方法添加,Mapping
類記錄了某個Page
的映射路徑初始化,會在第一使用的時候完成,使用
@Module
后的每個模塊內會生成一個RouterMapping_xxx
的類來初始化當前模塊的路由表,主模塊還需要使用@Modules
聲明,并會生成一個RouterInit
類以實現所有模塊的主模塊初始化的時候,調用各個模塊初始化自己的路由表
//APT 生成 public final class RouterInit { public static final void init() { RouterMapping_app.map(); RouterMapping_sdk.map(); } }
app
模塊的路由表初始化//APT 生成 public final class RouterMapping_app { public static final void map() { //路由表初始化 //... //@Router(value = "home/:homeName") com.github.mzule.activityrouter.router.Routers.map("home/:homeName", HomeActivity.class, null, extraTypes); //@Router("with_host") com.github.mzule.activityrouter.router.Routers.map("with_host", HostActivity.class, null, extraTypes); //@Router(value = {"http://mzule.com/main", "main", "home"}) com.github.mzule.activityrouter.router.Routers.map("http://mzule.com/main", MainActivity.class, null, extraTypes); com.github.mzule.activityrouter.router.Routers.map("main", MainActivity.class, null, extraTypes); com.github.mzule.activityrouter.router.Routers.map("home", MainActivity.class, null, extraTypes); //... } }
- 啟動頁面
最后通過
Routers#doOpen
來啟動一個Activity
private static boolean doOpen(Context context, Uri uri, int requestCode) { initIfNeed(); Path path = Path.create(uri); for (Mapping mapping : mappings) { if (mapping.match(path)) { if (mapping.getActivity() == null) { mapping.getMethod().invoke(context, mapping.parseExtras(uri)); return true; } Intent intent = new Intent(context, mapping.getActivity()); //... if (requestCode >= 0) { //... } else { context.startActivity(intent); } return true; } } return false; }
- 參數傳遞
可以通過
url
來傳遞一些基本類型的參數,如果需要傳遞對象類型的數據或者添加launchFlag
,只能通過Router#resolve
方法來返回一個Intent
對象,這無疑打破的代碼的簡潔和優雅性,可以考慮構造一個新的對象來負責跳轉和跳轉前額外參數的添加職責(后面說的 Rabbit 就更合理且優雅)-
具體流程
ActivityRouter啟動頁面流程.png -
類圖
ActivityRouter類圖
Rabbits
特點:編譯時注解、攔截器、mappings更新(似乎沒什么用?)、多組件支持、 Fragment 支持(需要自己擴展)
實現
-
page 的映射關系
根據
mappingsxxx.json
路由表文件(每次初始化的時候都需要讀取 json 文件,耗時會隨著頁面增多而增加),建立uri
=page
之間的聯系 ,模塊內使用了@Page(name = xxx)
注解的Activity
或Fragment
,編譯后會生成Router
類,記錄的模塊內所有page
的跳轉所需要的目標參數( 目標Fragment
的實例或者目標Activity
的class
),最后根據uri
可以得出目標page
名,再通過動態代理的方法來實現具體方法的反射調用mappings 表,指定了路徑、參數和對應
page
名:{ ... "mappings": { "demo://rabbits.kyleduo.com/test": "TEST", "demo://rabbits.kyleduo.com/test/{testing}": "TEST", //String 類型參數 "demo://rabbits.kyleduo.com/second/{id:l}": "SECOND", //long 類型,key 為 id "demo://rabbits.kyleduo.com/crazy/{name:s}/{age:i}/{finish:b}/end": "CRAZY" } }
APT
生成的Router
類,最后通過反射的方式調用public final class Router { //... public static final SecondFragment routeSecond() { return new SecondFragment(); } public static final Class routeTest() { return TestActivity.class; }
-
uri
的匹配,先整個uri
區配Mappings.java static Target match(Uri uri) { //...uri 在沒有 scheme 或者 host 的情況下會自動添加默認的 uri = builder.build(); Uri pureUri; //清除 fragment 和 query 片段 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { pureUri = builder.fragment(null).query(null).build(); } else { pureUri = builder.fragment(null).clearQuery().build(); } // Try to completely match. String page = sMappingsGroup.getMappings().get(pureUri.toString()); Bundle bundle = null; if (page == null) { // Deep match. bundle = new Bundle(); page = deepMatch(pureUri, bundle); } bundle = parseParams(uri, bundle); //query 片段的 key-value 存進 bundle //... if (page != null && !free) { // Match. Target target = new Target(uri); target.setPage(page); target.setExtras(bundle); target.setFlags(parseFlags(uri)); return target; } }
RESTful 風格的
uri
匹配private static String deepMatch(Uri pureUri, Bundle bundle) { Set<String> uris = sMappingsGroup.getMappings().keySet(); String[] source = pureUri.toString().split("(://|/)"); UriLoop: for (String uri : uris) { // Check match for each uri. String[] template = uri.split("(://|/)"); if (!template[0].equals(source[0]) || template.length != source.length) { continue; } if (!template[1].equals(source[1]) && (sMappingsGroup.getAllowedHosts() == null || !sMappingsGroup.getAllowedHosts().contains(source[1]))) { continue; } // Compare each part, parse params. for (int i = 2; i < source.length; i++) { String s = source[i]; String t = template[i]; if (t.equals(s)) { continue; } // Check whether a param field. if (t.matches("\\{\\S+(:\\S+)?\\}")) { // {id:l} or {testing} try { formatParam(t, s, bundle);//基本參數類型的解析 } catch (NumberFormatException e) { continue UriLoop; } continue; //參數都能匹配上才使用該 uri } continue UriLoop; } return sMappingsGroup.getMappings().get(uri); } return null; }
-
參數傳遞
通過
url
來傳參只能支持基本數據類型顯然是不夠用的,還需要提供額外的方式來做更多的處理,包括intentFlag
的添加,Rabbits#to
方法的產物AbstractNavigator
對象就可以處理更多的需求 -
具體流程
Rabbit頁面啟動.png
-
類圖
Rabbit類圖
小結
Rabbit
相對 ActivityRouter
來說功能更多,更具有可擴展性,Rabbit
的路由表需要從 json
文件中解析,這種方式來管理雖然可能使得路由看起來更清晰且可以動態更新,但畢竟是耗時操作,還是有些小顧慮,作者也提供了一個異步的加載方法,但我覺得可以考慮使用 ActivityRouter
的方式來實現更好,純靠 APT
來生成路由映射關系