基於Android平臺的RouterSDK設計與實現

Jomeslu發表於2017-02-23

本文會詳細介紹了RouterSDK框架的設計與實踐,通過這篇文章不但可以知道Router框架的一些功能,而且還可以提供實現SDK的一些思路。RouterSDK已經開源,下載地址:github.com/Jomes/route…

####背景
在很多場景中,你可能會遇到以下這些需求:第三方AP啟動本AP、網頁調起AP、網頁啟動指定某個頁面、某個頁面需要帶上引數、該引數可以指定型別等等。RouterSDK 很輕鬆的實現上面的功能,除此之外,RouterSDK還提供了一些實用的功能如動態路由配置、跳轉動畫、任務站、跳轉前處理等等。

####設計
Android系統正常的跳轉方式 A頁面 intent 到 B頁面,B頁面intent C頁面,他們之間的聯絡是很親密直接的。彼此間的跳轉都是相互intent,如下圖所示:

基於Android平臺的RouterSDK設計與實現
tmpdir--17_2_9_19_40_40.png

但是這樣的通訊方式太過直接,導致兩個頁面交集在一起,這時候想在他們之間做些事情是很難的,並且這樣的方式其實不利於解耦。假設每個頁面都是獨立的個體,他們之間的通訊通過一箇中轉器來處理,然後根據這個中轉器來啟動另外一個頁面。這樣我們就可以通過控制中轉器按照我們的規則跳轉。由於這個中轉器類似於路由器功能,所以把它叫做Router。
如下圖的模型所示:

基於Android平臺的RouterSDK設計與實現
tmpdir--17_2_9_19_47_07.png

RouterSDK 架構分為三層:核心層解析、業務層處理、API層呼叫。這種設計模型是比較經典的稱盒子模型。底層為核心層(不輕易改變)、中間層為業務層(處理業務邏輯)、外層為API層(提供AP呼叫)。
核心層解析:解析、bundle組裝、匹配路由
業務層處理:路由表操作、處理中斷器、跳轉、動畫等等。
API掉用:三方呼叫的API

基於Android平臺的RouterSDK設計與實現
洋蔥結構圖.png

為什麼這麼設計呢?首先是架構層次分明,通過這三個層次的隔離,從使用者的角度來看是很容易使用的,也對外隱藏了業務邏輯層、核心層的實現細節。而核心層定義的子系統抽象,保證了整個微社群SDK的靈活性、擴充套件性。

第三方AP啟動模型
應用內需要有個頁面接收第三方的請求,然後通過RouterSDK進行解析uri,轉換成相關的Intent,然後再跳轉到相關的頁面。如圖所示。
A頁面是程式的一個入口,跳轉交給Router處理。這樣我們就可以通過發命令來控制跳轉的頁面了。

基於Android平臺的RouterSDK設計與實現
三方AP啟動模型.png

####實現
首先編寫的是MatchParse類,最裡面的一層核心層,就是我上面所說的解析和組裝,這些我們認為是固定的不輕易改變的,這一層原則上呼叫者不需要知道具體怎麼去做。RouterSDK 將解析uri、拆解引數、組裝bundle,最終根據解析的結果以code形式返回上一層。然後在RouterEngine類處理業務相關的,如路由表的增刪改查、interceptor的編寫、跳轉動畫的實現等等。這一層可以根據自己需求增強SDK。這樣維護和擴充套件起來就非常的清晰了。最後編寫的是API,提供相應的API呼叫。三方的AP都是通過這層呼叫SDK的方法和規範入口。

基於Android平臺的RouterSDK設計與實現
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。
RouterSDk下載地址:github.com/Jomes/route…

###推薦
推薦一個學習原始碼的站點:androidblog.cn/index.php/S…

相關文章