基于Android平臺的RouterSDK設計與實現
本文會詳細介紹了RouterSDK框架的設計與實踐,通過這篇文章不但可以知道Router框架的一些功能,而且還可以提供實現SDK的一些思路。
背景
在很多場景中,你可能會遇到以下這些需求:第三方AP啟動本AP、網頁調起AP、網頁啟動指定某個頁面、某個頁面需要帶上參數、該參數可以指定類型等等。RouterSDK 很輕松的實現上面的功能,除此之外,RouterSDK還提供了一些實用的功能如動態路由配置、跳轉動畫、任務站、跳轉前處理等等。
設計
Android系統正常的跳轉方式 A頁面 intent 到 B頁面,B頁面intent C頁面,他們之間的聯系是很親密直接的。彼此間的跳轉都是相互intent,如下圖所示:
但是這樣的通信方式太過直接,導致兩個頁面交集在一起,這時候想在他們之間做些事情是很難的,并且這樣的方式其實不利于解耦。假設每個頁面都是獨立的個體,他們之間的通信通過一個中轉器來處理,然后根據這個中轉器來啟動另外一個頁面。這樣我們就可以通過控制中轉器按照我們的規則跳轉。由于這個中轉器類似于路由器功能,所以把它叫做Router。
如下圖的模型所示:
RouterSDK 架構分為三層:核心層解析、業務層處理、API層調用。這種設計模型是比較經典的稱盒子模型。底層為核心層(不輕易改變)、中間層為業務層(處理業務邏輯)、外層為API層(提供AP調用)。
核心層解析:解析、bundle組裝、匹配路由
業務層處理:路由表操作、處理中斷器、跳轉、動畫等等。
API掉用:三方調用的API
洋蔥結構圖.png
為什么這么設計呢?首先是架構層次分明,通過這三個層次的隔離,從用戶的角度來看是很容易使用的,也對外隱藏了業務邏輯層、核心層的實現細節。而核心層定義的子系統抽象,保證了整個微社區SDK的靈活性、擴展性。
第三方AP啟動模型
應用內需要有個頁面接收第三方的請求,然后通過RouterSDK進行解析uri,轉換成相關的Intent,然后再跳轉到相關的頁面。如圖所示。
A頁面是程序的一個入口,跳轉交給Router處理。這樣我們就可以通過發命令來控制跳轉的頁面了。
三方AP啟動模型.png
實現
首先編寫的是MatchParse類,最里面的一層核心層,就是我上面所說的解析和組裝,這些我們認為是固定的不輕易改變的,這一層原則上調用者不需要知道具體怎么去做。RouterSDK 將解析uri、拆解參數、組裝bundle,最終根據解析的結果以code形式返回上一層。然后在RouterEngine類處理業務相關的,如路由表的增刪改查、interceptor的編寫、跳轉動畫的實現等等。這一層可以根據自己需求增強SDK。這樣維護和擴展起來就非常的清晰了。最后編寫的是API,提供相應的API調用。三方的AP都是通過這層調用SDK的方法和規范入口。
Class Diagram.png
MatchParse 解析 uri,根據rule轉化成不同的類型的參數,最終轉成我們需要的產物bundle。這個規則是約定好的,RouterSDK的規則表如下:
key format | {i:ikey} | {f:key} | {l:key} | {d:key} | {s:key} | {b:key} |
---|---|---|---|---|---|---|
type | integer | float | long | double | string | boolean |
如:jomeslu://www?{i:id}=168&{s:jomeslu}=jomeslu
- Scheme:通常定義為 應用某個路徑
- Host: 通常用于區分不同的頁面,比如activity
- path : 傳遞參數與參數類型
舉個例子,jomeslu://www?{i:id}=168&{s:jomeslu}=jomeslu ,SDK 會在路由表查找jomeslu://www,找到對應的Class文件,而{i:id}=168&{s:jomeslu}=jomeslu,根據路由規則轉換換成 int id=168 ,String jomeslu = jomslu.然后將這些值換成bundle進行傳值。接bundle值跟系統的寫法是一樣的, int id = getIntent().getIntExtra("id", -1);所以RouterSDK不用關心如何接受值。
private void setKeyValueBundle(String key, String value, Bundle bundle) throws Exception {
//符合自定義的規則 {s:te}
if (!TextUtils.isEmpty(key) && key.startsWith(RuleConstant.PARAM_SPIT_LEFT) && key.endsWith(RuleConstant.PARAM_SPIT_RIGHT) &&
key.contains(RuleConstant.PARAM_SPIT_SIGN)) {
String realKey = key.substring(key.indexOf(RuleConstant.PARAM_SPIT_SIGN) + 1, key.lastIndexOf(RuleConstant.PARAM_SPIT_RIGHT));
String type = key.substring(1, key.indexOf(RuleConstant.PARAM_SPIT_SIGN));
switch (type.toUpperCase()) {
case RuleConstant.PARAM_B:
Boolean aBoolean = Boolean.valueOf(value);
bundle.putBoolean(realKey, aBoolean);
break;
case RuleConstant.PARAM_D:
bundle.putDouble(realKey, Double.valueOf(value));
break;
case RuleConstant.PARAM_F:
bundle.putFloat(realKey, Float.valueOf(value));
break;
case RuleConstant.PARAM_I:
bundle.putInt(realKey, Integer.valueOf(value));
break;
case RuleConstant.PARAM_L:
bundle.putLong(realKey, Long.valueOf(value));
break;
case RuleConstant.PARAM_S:
bundle.putString(realKey, value);
break;
}
} else if (!TextUtils.isEmpty(key)) {
//默認是字符串
bundle.putString(key, value);
}
}
RouterEngine 的 getIntent(Context context) 的方法,這個根據MatchParse解析結果 返回相關的intent。根據不同mathcode 我們就知道底層處理了的結果。以后擴展起來就比較方便了。
MatchParse mathchParse = new MatchParse(mRouteTableHandle, context);
int mathchCode = mathchParse.mathch(param);
switch (mathchCode) {
.....
.....
case RuleConstant.MATHCH_SUCCESS:
if (param.getmRouterResultCallback() != null) {
param.getmRouterResultCallback().succeed(param.getUri());
}
if (param.getIRouteInterceptor() != null && param.getIRouteInterceptor().interceptor()) {
return null;
}
Intent intent = new Intent(context, param.getClazz());
intent.putExtras(param.getmBundle());
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
...
不一一貼代碼了,具體請查看代碼。RouterSDK已經開源。在本文已經寫出下載地址。
總結
本文講解了RouterSDK 的設計與實現,以及編寫SDK的技巧。同時講解了Router框架的優勢以及使用場景。如果有好像的想法請提Issues。
來自:http://www.jianshu.com/p/c7524b0125df