一個Android路由框架的誕生之路

通過前面三篇文章,相信你們對組件化都有了必定程度的理解。bash

在這個過程當中一直強調了組件化的一個基礎設施:路由!沒有它組件化能夠說是步履維艱,本篇文章咱們就來談談一個組件化路由框架誕生過程當中的那些思考微信

本文概述

一、爲何須要路由

這個問題其實咱們以前談到過,並且有過組件化實踐或者嘗試的同窗必定有切身感覺。明確一個前提:各個業務模塊之間不會是相互隔離而是必然存在一些交互的;框架

  • 在Module A須要跳轉到Module B某界面,而咱們通常都是使用強引用的Class顯式的調用;
  • 在Module A須要調用Module B提供的方法,例如別的Module調用用戶模塊退出登陸的方法;

這兩種調用形式你們很容易明白,正常開發中你們也是絕不猶豫的調用。可是咱們在組件化開發的時候卻有很大的問題:ide

  • 模塊B的Activity Class在本身的Module B,那Module A必然引用不到,顯式跳轉行不通;
  • 同理,直接去調用某個Module的方法也行不通;

由此:必然須要一種支持組件化需求的交互方式,提供UI跳轉以及方法調用的能力。組件化

二、一個路由庫須要知足什麼

首先這個路由庫也是一個技術組件,在總體組件化層次的設計中處於Lib層,做爲一項基礎庫。那麼路由庫不只僅須要知足自身的能力,同時勢必要知足一項基礎庫該有的條件:動畫

  • Api友好,接入簡單、低成本
  • 具有UI跳轉和方法調用的能力
  • 功能穩定
  • 可定製化

三、淘汰過的方式

任何系統或框架,雖然在高版本中看起來都很完美,可是實際上一開始並不是就是如此,都是一步步實踐、迭代改善到基於當前相對完美狀態的。好比咱們以前就思考過以下方式:ui

3.一、基於隱式意圖

各位老司機都知道,Android中打開一個Activity,能夠有兩種方式,顯示意圖和隱式意圖。既然顯式意圖致使了強引用,那麼咱們使用隱式意圖,既能夠打開Activity,同時也不會形成Module間的強引用。this

評價:這種方式確實能夠完成路由的UI跳轉功能,可是依賴於Manifest文件的修改,同時參數也存在不便傳遞的問題,所以不作推薦。url

3.二、基於事件,使用廣播或EventBus

這種思路也很容易想到,既然不能直接交互,那麼就隱式的來,在須要交互的地方發通知,而後接收方根據不一樣的通知類型作出不一樣的處理。spa

/**
     * EventBus的事件類
     */
    public class InteractEvent {
        public int type;
        public String param;// eg:String類型參數一
        public ParamObject paramObject;// eg:Object類型參數二
    }

    /**
     * 處理不一樣的交互設置
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(InteractEvent event) {
        if(event.type == EventType.JUMP_LOGINACTIVITY){
            Intent intent = new Intent(mContext,LoginActivity.class);
            intent.putExtra("param",event.param);
            intent.putExtra("paramObject",event.paramObject);
            mContext.startActivity(intent);
        }
    }
複製代碼

評價:這種交互的方式是可行的,可是能夠明顯看出,比較複雜,對於界面跳轉比較多的場景,接入及維護成本較高。

3.三、調用一個固定的方法

咱們在須要交互的類中加上方法,方法簽名固定,而後給交互類打上一個標籤。這樣在別的組件中咱們須要這個交互類的時候經過標籤拿到,調用這個固定的方法。思路是這樣,如下提供一種方式的僞代碼,有不一樣的實現。

public interface IDoAction{
        void doAction(HashMap hashMap);
    }
    
    public class LoginActivity extends Activity implements IDoAction{
        public void doAction(HashMap hashMap){
            // 1.獲取參數、Type
            hashMap.get();
            // 2.跳轉
        }
    }
    
    // 調用
    Dispatcher.get(url).doAction(hashMap);
複製代碼

備註使用HashMap做爲參數的緣由:每一個交互類須要的參數不同,而方法簽名必須固定才能經過接口去調用,傳遞HashMap這個參數能夠包含多個不一樣類型的參數。

評價:最不推薦,使用繁瑣,侵入性太強,改形成本極大。

四、一種好的路由實踐

總結以上幾種很差的實踐方式,都在於侵入性強,接入及維護成本高等。那反過來推就是一個好的路由須要具有低侵入性、易接入、自動化等特性。 上述第三種方案咱們能夠吸取一點的是每一個交互類打上一個標籤,記錄這個映射關係,方便在別的Module進行獲取。

eg: easyrouter://main/Detail    ————》 MainDetailActivity
複製代碼

順着這個映射往下想,這個映射保存了標籤和Activity,那麼打開Activity只須要知道這個標籤便可。舉例:標籤A和ActivityA對應,那麼咱們只要遇到標籤A就知道它想要打開的是ActivityA。同時若是咱們處理好了打開Activity須要的傳參問題就離自動化邁出了一大步。

問題就簡化成了兩個:

  1. 映射關係,咱們可使用String字符串做爲標籤,既保證通用性又能夠保證惟一性。利用一個Map保存這個字符串和Activity的映射關係,這樣能夠保證在別的Module能經過字符串獲取到咱們須要的Activity
  2. 傳參以及Activity各類特性(利用動畫、onActivityResult生命週期)的支持

關於第二個問題實際上就是將這個字符串儘量多的解析到Android多須要的數據,好比參數傳遞、動畫、生命週期等。關於這個解析能夠有兩種方式:

  1. 直接簡單粗暴在String後面拼一個參數,這個參數的格式是Json,到達目標界面以後目標界面再去解析;
  2. 制定必定規則經過路由就解決好,到目標界面直接像正常Android開發同樣去獲取;
eg:     easyrouter://routertest?name=liuzhao&company=mycompany
複製代碼

五、方法調用的實現

方法調用看起來均可以經過上述:基於事件及調用一個固定的方法等方式來實現,可是使用起來一定複雜無比,各個Module之間交互不只改造困難,維護成本也很高。

注意各個Module向外提供的方法一定不同:須要不一樣的方法簽名。並且從改造及維護成本考慮,最好能夠像是在一個Module裏同樣直接調用,IDE能夠自動提示出來方法參數。

那咱們就想把Module向外提供的方法內聚到一個類裏,只向外暴露這個類,簡稱這個Module的交互服務類。這樣別的Module調用的時候就能夠想直接調用普通類的方法同樣簡單方便了。

那咱們就剩下一個問題:別的Module如何獲取你的交互服務類呢?很容易想到上面提到的映射,可是此種場景下若是使用字符串作Key真的能夠嗎? 若是使用字符串作Key,別的Module拿到的Value只能肯定是一個Class,具體的Class類型卻不清楚,調用具體的方法尤爲是被IDE提示,更是不可能。問題又簡化成了如何讓咱們知道拿到的Class中有哪些方法呢?

通過屢次思考,終於想到了一個解決方案:Module須要向外暴露的方法,咱們經過一個Interface來定義,這個Interface定義在Lib層也就是說每一個Module均可以訪問到,而保存映射關係的Key咱們也使用這個Interface。那麼映射表裏保存的就是:

private static Map<Module暴露接口Interface, Module暴露接口的實現類InterfaceImpl> moduleInteracts = new HashMap<>();
複製代碼

那麼別的Module在獲取這個服務類時就能夠直接經過在Lib層定義的Interface來獲取,而後經過泛型轉換成這個接口,然後直接調用相應方法便可,就像調用一個普通方法同樣簡單

public static <T extends IBaseModuleService> T getModuleService(Class<T> tClass) {
    if (moduleInteracts.containsKey(tClass)) {
        return (T) moduleInteracts.get(tClass);
    }
    return null;
}

調用:EasyRouter.getModuleService(BaseModuleService.ModuleInteractService.class).runModuleInteract(MainActivity.this);
               
複製代碼

六、路由的最佳實踐

6.一、編譯時註解

通過4、五兩節咱們知道了路由相對較好的實踐,但同時咱們可否讓這個過程自動化呢?其實能夠藉助編譯時註解技術自動生成映射表,這樣在接入的時候就更加簡單方便,只須要在對應的Activity上打上一個註解,配置相應的字符串,這個映射表就自動生成。

@DisPatcher("easyrouter://routertest")
public class MainActivity extends Activity {
    ......
}

生成代碼

@Override
public void initActivityMap(HashMap<String, Class> activityMap) {
    activityMap.put("easyrouter://routertest", MainActivity.class);
}

複製代碼

6.二、攔截器

6.2.一、統一判斷

在實際開發中,咱們常常會遇到些統一的操做,好比某些應用是須要用戶先登錄的,那麼在用戶瀏覽以後的下一步操做時用戶進行各類點擊都須要進行判斷是否登錄,未登陸則跳轉到登錄界面,登錄以後則放行。

正常狀況咱們須要在每個點擊的地方進行判斷,可是明顯費時費力,既然咱們已經作了路由,全部的界面跳轉都須要通過咱們,那咱們天然能夠進行統一的判斷,在路由進行分發時候進行判斷,知足攔截器條件則進行攔截。

6.2.二、重定向

若是咱們須要對App的功能進行A/BTest,咱們該如何進行呢?方式確定有不少,可是不必定通用。注意咱們已經有了路由,結合路由來作A/BTest的話更加方便:

  • 首先咱們給路由加一個攔截器,每一條跳轉都會通過這個攔截器的判斷;
  • 經過路由實現界面跳轉,在路由解析過程當中咱們識別到了須要跳轉的是A模塊;
  • 通過攔截器的判斷,若是A/BTest實驗命中的是B模塊,則將這個路由進行重定向到B模塊;

備註:重定向的好處還有更多,好比緊急狀況下的熱修復替換成H5界面等。

6.三、過程監聽

就是監聽打開Activity的過程,如

  • 打開前進行數據的準備;
  • 打開後的回調;
  • 未匹配到目標Activity的降級等;

本文主要介紹一個Android路由框架誕生過程當中的思考,在下篇文章將會具體推薦一個路由框架。

廣告時間

今日頭條各Android客戶端團隊招人火爆進行中,各個級別和應屆實習生都須要,業務增加快、日活高、挑戰大、待遇給力,各位大佬走過路過千萬不要錯過!

本科以上學歷、非頻繁跳槽(如兩年兩跳),歡迎加個人微信詳聊:KOBE8242011

歡迎關注
相關文章
相關標籤/搜索