貨拉拉 Android 模塊化路由框架:TheRouter

TheRouter 是一個(gè) Kotlin 編寫字柠,用于 Android 模塊化開發(fā)的一整套解決方案框架窑业。
Github 項(xiàng)目地址與使用文檔詳見 https://github.com/HuolalaTech/hll-wp-therouter-android常柄。

TheRouter 核心功能具備如下能力:

  • 頁面導(dǎo)航跳轉(zhuǎn)能力(Navigator)
  • 跨模塊依賴注入能力(ServiceProvider)
  • 單模塊自動(dòng)初始化能力(FlowTaskExecutor)
  • 動(dòng)態(tài)化能力(ActionManager)
  • 模塊AAR/源碼依賴一鍵切換腳本

一拐纱、為什么要使用 TheRouter

路由是現(xiàn)如今移動(dòng)端開發(fā)中必不可少的功能哥倔,尤其是企業(yè)級(jí)APP咆蒿,可以用于將Intent頁面跳轉(zhuǎn)的強(qiáng)依賴關(guān)系解耦沃测,同時(shí)減少跨團(tuán)隊(duì)開發(fā)的互相依賴問題蒂破。

對(duì)于大型 APP 開發(fā),基本都會(huì)選用模塊化(或組件化)方式開發(fā)惧互,對(duì)于模塊間解耦要求更高喊儡。 TheRouter 是一整套完全面向模塊化開發(fā)的解決方案艾猜,不僅能支持常規(guī)的模塊依賴解耦匆赃、頁面跳轉(zhuǎn),同時(shí)提供了模塊化過程中常見問題的解決辦法钱床。例如:完美解決了模塊化開發(fā)后由于組件內(nèi)無法獲取 Application 生命周期與業(yè)務(wù)流程查牌,造成每次初始化與關(guān)聯(lián)依賴調(diào)用都需要跨模塊修改代碼的問題纸颜。

1.1 TheRouter 四大能力

Navigator:

  • 支持 ActivityFragment
  • 支持Path與頁面多對(duì)一關(guān)系或一對(duì)一關(guān)系胁孙,可用于解決多端path統(tǒng)一問題
  • 頁面Path支持正則表達(dá)式聲明
  • 支持 json 格式路由表導(dǎo)出
  • 支持動(dòng)態(tài)下發(fā) json 路由表称鳞,降級(jí)任意頁面為H5
  • 支持任意object跨模塊傳遞(無需序列化冈止,且能保證對(duì)象類型)
  • 支持頁面跳轉(zhuǎn)攔截處理
  • 支持自定義頁面參數(shù)解析方式(例如將json解析為對(duì)象)
  • 支持使用路由跳轉(zhuǎn)到第三方 SDK 中的Activity(Fragment)

ServiceProvider:

  • 支持跨模塊依賴注入
  • 支持自定義注入項(xiàng)的創(chuàng)建規(guī)則熙暴,依賴注入可自定義參數(shù)
  • 支持自定義服務(wù)攔截周霉,單模塊mock調(diào)試
  • 支持注入對(duì)象緩存俱箱,多次注入 只會(huì)new一次對(duì)象

FlowTaskExecutor:

  • 支持單模塊獨(dú)立初始化
  • 支持懶加載初始化
  • 獨(dú)立初始化允許多任務(wù)依賴(參考Gradle Task)
  • 支持編譯期循環(huán)引用檢測
  • 支持自定義業(yè)務(wù)初始化時(shí)機(jī),可以用于解決隱私合規(guī)問題

ActionManager:

  • 支持全局回調(diào)配置
  • 支持優(yōu)先級(jí)響應(yīng)與中斷響應(yīng)
  • 支持記錄調(diào)用路徑厂财,解決調(diào)試期觀察者模式無法追蹤Observable的問題

注: FlowTaskExecutorActionManager 后續(xù)會(huì)作為可選能力与斤,提供可插拔單獨(dú)使用的選項(xiàng)(預(yù)計(jì)10月份提供)撩穿。

二食寡、路由方案

目前現(xiàn)有的路由基本上集中于兩種能力的實(shí)現(xiàn):頁面跳轉(zhuǎn)抵皱、跨模塊調(diào)用呻畸,核心技術(shù)方案大體上如圖:

1.png
  1. 開發(fā)階段伤为,對(duì)要使用路由的落地頁或被調(diào)用方法添加注解標(biāo)識(shí)。
  2. 編譯期解析注解叙甸,生成一系列中間代碼裆蒸,待調(diào)用光戈。
  3. 應(yīng)用啟動(dòng)后調(diào)用中間代碼完成路由的準(zhǔn)備動(dòng)作久妆。大部分路由會(huì)額外通過 Gradle Transform跷睦,在編譯期做一次聚合,以提升運(yùn)行時(shí)準(zhǔn)備路由表的效率爹殊。
  4. 發(fā)起路由跳轉(zhuǎn)時(shí)梗夸,本質(zhì)上就是一次路由表遍歷反症,通過uri獲取到對(duì)應(yīng)的落地頁或方法對(duì)象铅碍,進(jìn)行調(diào)用胞谈。

TheRouter 的頁面跳轉(zhuǎn)憨愉、跨模塊調(diào)用也是如此配紫,但是在設(shè)計(jì)上會(huì)有一些細(xì)節(jié)處理笨蚁。

2.png

TheRouter 會(huì)在編譯期根據(jù)注解生成 RouteMap__開頭的類括细,這些類中記錄了當(dāng)前模塊的所有路由信息奋单,也就是當(dāng)前模塊的路由表览濒。

在最頂層的app模塊中,通過Gradle插件应又,將所有aar株扛、源碼中的RouteMap__開頭的類統(tǒng)一集中到TheRouterServiceProvideInjecter類中洞就。

后續(xù)應(yīng)用啟動(dòng)后旬蟋,初始化路由時(shí)只需要執(zhí)行TheRouterServiceProvideInjecter類的方法倾贰,就能沒有任何反射的加載到全部的路由表了躁染。

加載以后的路由表會(huì)被保存到一個(gè)支持正則匹配的 Map 中吞彤,這也是TheRouter允許多個(gè)path對(duì)應(yīng)同一個(gè)落地頁的原因饰恕。每當(dāng)發(fā)生頁面跳轉(zhuǎn)時(shí)埋嵌,通過跳轉(zhuǎn)時(shí)的path俱恶,去Map中獲取到對(duì)應(yīng)的落地頁信息合是,再正常調(diào)用startActivity()即可聪全。

三难礼、使用 TheRouter 頁面跳轉(zhuǎn)

3.1 聲明路由項(xiàng)

如果一個(gè)頁面(支持 Activity蛾茉、Fragment)允許被路由打開谦炬,則需要使用注解 @Route 聲明路由項(xiàng),每個(gè)頁面允許聲明多個(gè)路由項(xiàng)散劫,也就是一對(duì)多的能力获搏,極大降低多端路由統(tǒng)一時(shí)的業(yè)務(wù)影響面常熙。

參數(shù)釋義

  • path: 路由path 【必傳】裸卫。
    建議是一個(gè)url墓贿。path內(nèi)支持使用正則表達(dá)式(為了匹配效率聋袋,正則必須包含反雙斜杠\)幽勒,允許多個(gè)path對(duì)應(yīng)同一個(gè)Activity(Fragment)啥容。
  • action: 自定義事件【可選】顷霹。
    一般用來打開目標(biāo)頁面后做一個(gè)執(zhí)行動(dòng)作泼返,例如自定義頁面彈出廣告彈窗绅喉。
  • description: 頁面描述【可選】柴罐。
    會(huì)被記錄到路由表中革屠,方便后期排查的時(shí)候知道每個(gè)path或Activity是什么業(yè)務(wù)。
  • params: 頁面參數(shù)【可選】板甘。
    自動(dòng)寫入intent中盐类,允許寫在路由表中動(dòng)態(tài)下發(fā)修改默認(rèn)值,或通過路由跳轉(zhuǎn)時(shí)代碼傳入呛谜。
@Route(path = "http://therouter.com/home", action = "action://scheme.com",
       description = "第二個(gè)頁面", params = {"hello", "world"})
public class HomeActivity extends AppCompatActivity {
}

3.2 發(fā)起頁面跳轉(zhuǎn)

傳入的參數(shù)可以是 String 和8種基本數(shù)據(jù)類型在跳、也可以是BundleSerializable隐岛、
Parcelable對(duì)象猫妙,跟 Intent 傳值規(guī)則一致。
同時(shí)也支持為本次跳轉(zhuǎn)的 Intent 添加Flag/Uri/ClipData/identifier等業(yè)務(wù)特殊參數(shù)割坠。

// 傳入?yún)?shù)可以通過注解 @Autowired 解析成任意類型,如果是對(duì)象建議傳json
// context 參數(shù)如果不傳或傳 null元践,會(huì)自動(dòng)使用 application 替換
TheRouter.build("http://therouter.com/home")
        .withInt("key1", 12345678)
        .withString("key2", "參數(shù)")
        .withBoolean("key3", false)
        .withSerializable("key4", object)
        .withObject("object", any) // 這個(gè)方法可以傳遞任意對(duì)象,但是接收的地方對(duì)象類型需自行保證一致童谒,否則會(huì)強(qiáng)轉(zhuǎn)異常
        .navigation(context);

        // 如果傳入 requestCode单旁,默認(rèn)使用startActivityForResult啟動(dòng)Activity
        .navigation(context, 123);

        // 如果要打開的是fragment,需要使用
        .createFragment();

3.3 路由表生成規(guī)則

如果兩條路由的path饥伊、目標(biāo)className完全相同象浑,則認(rèn)為是同一條路由,不會(huì)考慮參數(shù)是否相同琅豆。
路由表生成規(guī)則:編譯期按照如下順序取并集愉豺。

覆蓋規(guī)則
根據(jù)如下順序,如果相同茫因,后者可以覆蓋前者的路由表規(guī)則蚪拦。

  1. 編譯期解析注解生成路由表
  2. 首先取 業(yè)務(wù)模塊 aar 中的路由表
  3. 再取 主app module 代碼中的路由表
  4. 最后取 assets/RouteMap.json 文件中聲明的路由表。
  • 如果編譯期沒有這個(gè)文件冻押,會(huì)生成一份默認(rèn)路由表放在這個(gè)目錄內(nèi)驰贷;如果有,會(huì)將路由表合并洛巢。
  • 路由表生成時(shí)可配置是否啟用檢查路由合法性括袒,判斷目標(biāo)頁面是否存在,(warning/error)級(jí)別稿茉。
  1. 運(yùn)行時(shí)線上動(dòng)態(tài)下發(fā)的路由表
  • 路由表允許線上動(dòng)態(tài)下發(fā)锹锰,將覆蓋本地路由表芥炭,詳見 【3.4 動(dòng)態(tài)路由表的設(shè)計(jì)與使用】

如果編譯期沒有這個(gè)文件,會(huì)生成一份默認(rèn)路由表放在這個(gè)目錄內(nèi)恃慧;如果有园蝠,會(huì)將路由表合并,因此糕伐,對(duì)于沒辦法修改代碼的第三方SDK內(nèi)部砰琢,如果希望通過路由打開,只需要手動(dòng)在RouteMap.json文件中聲明良瞧,就能通過路由打開了陪汽。

3.4 動(dòng)態(tài)路由表的設(shè)計(jì)與使用

TheRouter 的路由表是動(dòng)態(tài)添加的,項(xiàng)目每次編譯后褥蚯,會(huì)在 apk 內(nèi)生成一份當(dāng)前 APP 的全量路由表挚冤,默認(rèn)路徑為:/assets/therouter/routeMap.json。這個(gè)路由表也可以后續(xù)通過遠(yuǎn)程下發(fā)的方式使用赞庶,例如遠(yuǎn)端可以針對(duì)不同的APP版本训挡,下發(fā)不同的路由表達(dá)到配置目的。這樣如果將來線上某些頁面發(fā)生Crash歧强,可以通過將這個(gè)頁面的落地頁替換為H5的方式澜薄,臨時(shí)解決這類問題。

有兩種推薦的遠(yuǎn)程下發(fā)方式可供使用方選擇:

  1. 將打包系統(tǒng)與配置系統(tǒng)打通摊册,每次新版本APP打包后自動(dòng)將assets/目錄中的配置文件上傳到配置系統(tǒng)肤京,下發(fā)給對(duì)應(yīng)版本APP 。優(yōu)點(diǎn)在于全自動(dòng)不會(huì)出錯(cuò)茅特。
  2. 配置系統(tǒng)無法打通忘分,線上手動(dòng)下發(fā)需要修改的路由項(xiàng),因?yàn)?TheRouter 會(huì)自動(dòng)用最新下發(fā)的路由項(xiàng)覆蓋包內(nèi)的路由項(xiàng)白修。優(yōu)點(diǎn)在于精確妒峦,且流量資源占用小。

注:一旦你設(shè)置了自定義的InitTask兵睛,原框架內(nèi)路由表初始化任務(wù)將不再執(zhí)行肯骇,你需要自己處理找不到路由表時(shí)的兜底邏輯,一種建議的處理方式見如下代碼祖很。

// 此代碼 必須 在 Application.super.onCreate() 之前調(diào)用
RouteMap.setInitTask(new RouterMapInitTask() {
    /** 
     * 此方法執(zhí)行在異步
     */
    @Override
    public void asyncInitRouteMap() {
        // 此處為純業(yè)務(wù)邏輯累盗,每家公司遠(yuǎn)端配置方案可能都不一樣
        // 不建議每次都請(qǐng)求網(wǎng)絡(luò),否則請(qǐng)求網(wǎng)絡(luò)的過程中突琳,路由表是空的若债,可能造成APP無法跳轉(zhuǎn)頁面
        // 最好是優(yōu)先加載本地,然后開異步線程加載遠(yuǎn)端配置
        String json = Connfig.doHttp("routeMap");
        // 建議加一個(gè)判斷拆融,如果遠(yuǎn)端配置拉取失敗蠢琳,使用包內(nèi)配置做兜底方案啊终,否則可能造成路由表異常
        if (!TextUtils.isEmpty(json)) {
            List<RouteItem> list = new Gson().fromJson(json, new TypeToken<List<RouteItem>>() {
            }.getType());
            // 建議遠(yuǎn)端下發(fā)路由表差異部分,用遠(yuǎn)端包覆蓋本地更合理
            RouteMap.addRouteMap(list);
        } else {
            // 在異步執(zhí)行TheRouter內(nèi)部兜底路由表
            initRouteMap()
        }
    }
});

3.5 高級(jí)用法

TheRouter同時(shí)支持更多頁面跳轉(zhuǎn)能力傲须,詳情可參考項(xiàng)目文檔【https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/Navigator】:

  • 為第三方庫里面的頁面添加路由表蓝牲,達(dá)到對(duì)某些頁面降級(jí)替換的目的;
  • 延遲路由跳轉(zhuǎn)(從Android 8開始泰讽,不能在后臺(tái)啟動(dòng)頁面)例衍;
  • 跳轉(zhuǎn)過程攔截器(總共四層,可根據(jù)實(shí)際需求使用)已卸;
  • 跳轉(zhuǎn)結(jié)果回調(diào)佛玄;

四、跨模塊依賴注入 ServiceProvider 的設(shè)計(jì)

對(duì)于模塊化開發(fā)中跨模塊的調(diào)用累澡,我們推薦采用 SOA(面向服務(wù)架構(gòu)) 的設(shè)計(jì)方式梦抢,服務(wù)調(diào)用方與使用方完全隔離,調(diào)用模塊外的能力不需要關(guān)注能力的提供者是誰愧哟。
ServiceProvider 的核心設(shè)計(jì)思想也是這樣的奥吩,目前服務(wù)間的調(diào)用協(xié)議采用接口的方式。當(dāng)然蕊梧,也可以兼容不通過接口下沉而是直接調(diào)用的情況霞赫。

3.jpeg

具體到 Android 側(cè)就是 AIDL 類似的設(shè)計(jì),只是要比AIDL開發(fā)簡單很多:

  • 服務(wù)提供方負(fù)責(zé)提供服務(wù)肥矢,不需要關(guān)心調(diào)用方是誰會(huì)在何時(shí)調(diào)用自己端衰。
  • 服務(wù)的使用方只關(guān)注服務(wù)本身,不需要關(guān)心這個(gè)服務(wù)是誰提供的橄抹,只需要只能服務(wù)能提供哪些能力即可靴迫。

例如上面的圖片:拉拉需要使用錄音的服務(wù)惕味,小貨則向外提供一個(gè)錄音的服務(wù)楼誓,由TheRouterServiceProvider負(fù)責(zé)撮合。

4.1 服務(wù)使用方:拉拉

她無需關(guān)心名挥,IRecordService這個(gè)接口服務(wù)是誰提供的疟羹,他只需要知道自己需要使用這樣的一個(gè)服務(wù)就行了。
注:如果沒有提供服務(wù)的提供方禀倔,TheRouter.get()可能返回null

TheRouter.get(IRecordService::class.java)?.doRecord()

4.2 服務(wù)提供方:小貨

服務(wù)提供方需要聲明一個(gè)提供服務(wù)的方法榄融,用@ServiceProvider注解標(biāo)記。

  • 如果是 java救湖,必須是 public static 修飾
  • 如果是 kotlin愧杯,建議寫成 top level 的函數(shù)
  • 方法名不限
/**
 * 方法名不限定,任意名字都行
 * 返回值必須是服務(wù)接口名鞋既,如果是實(shí)現(xiàn)了服務(wù)的子類力九,需要加上returnType限定(例如下面代碼)
 * 方法必須加上 public static 修飾耍铜,否則編譯期就會(huì)報(bào)錯(cuò)
 */
@ServiceProvider
public static IRecordService test() {
    return new IRecordService() {
        @Override
        public void doRecord() {
            String str = "執(zhí)行錄制邏輯";
        }
    };
}

// 也可以直接返回對(duì)象,然后標(biāo)注這個(gè)方法的服名是什么
@ServiceProvider(returnType = IRecordService.class)
public static RecordServiceImpl test() {
    // xxx 
}

五跌前、單模塊自動(dòng)初始化能力 FlowTaskExecutor 的設(shè)計(jì)

前面講過棕兼,TheRouter是完全面向模塊化開發(fā)提供的一套解決方案。在模塊化開發(fā)時(shí)抵乓,可能每個(gè)模塊都有自己需要初始化的一些代碼伴挚。以前的做法是把這些代碼都在Application里聲明,但是這樣可能隨著業(yè)務(wù)變動(dòng)每次都需要修改Application所在模塊灾炭。TheRouter 的單模塊自動(dòng)初始化能力就是為了解決這樣的情況茎芋,可以只在當(dāng)前模塊聲明初始化方法后,將會(huì)在業(yè)務(wù)場景時(shí)自動(dòng)被調(diào)用咆贬。

每個(gè)希望被自動(dòng)初始化的方法败徊,必須使用public static修飾,主要原因是這樣子就能通過類名直接調(diào)用了掏缎。另外很多初始化代碼都需要獲取Context對(duì)象皱蹦,所以我們將Context作為初始化方法的默認(rèn)參數(shù),會(huì)自動(dòng)傳入Application眷蜈。其他的所在類名沪哺、方法名都沒有限制,反正只要加上了 @FlowTask 注解酌儒,在編譯期都能通過 APT 獲取到辜妓。

5.1 FlowTaskExecutor 使用介紹

可以在當(dāng)前模塊中,任意類中聲明一個(gè)任意方法名的方法忌怎,給方法添加上@FlowTask 的注解即可籍滴。

@FlowTask 注解參數(shù)說明:

  • taskName:當(dāng)前初始化任務(wù)的任務(wù)名,必須全局唯一榴啸,建議格式為:moduleName_taskName
  • dependsOn:參考Gradle Task孽惰,任務(wù)與任務(wù)之間可能會(huì)有依賴關(guān)系。如果當(dāng)前任務(wù)需要依賴其他任務(wù)先初始化鸥印,則在這里聲明依賴的任務(wù)名勋功。可以同時(shí)依賴多個(gè)任務(wù)库说,用英文逗號(hào)分隔狂鞋,空格可選,會(huì)被過濾:dependsOn = "mmkv, config, login"潜的,默認(rèn)為空骚揍,應(yīng)用啟動(dòng)就被調(diào)用
  • async:是否要在異步執(zhí)行此任務(wù),默認(rèn)false啰挪。
/**
 * 將會(huì)在異步執(zhí)行
 */
@FlowTask(taskName = "mmkv_init", dependsOn = TheRouterFlowTask.APP_ONCREATE, async = true)
public static void test2(Context context) {
    System.out.println("異步=========Application onCreate后執(zhí)行");
}

@FlowTask(taskName = "app1")
public static void test3(Context context) {
    System.out.println("main線程=========應(yīng)用啟動(dòng)就會(huì)執(zhí)行");
}

/**
 * 將會(huì)在主線程初始化
 */
@FlowTask(taskName = "test", dependsOn = "mmkv,app1")
public static void test3(Context context) {
    System.out.println("main線程=========在app1和mmkv兩個(gè)任務(wù)都執(zhí)行以后才會(huì)被執(zhí)行");
}

5.2內(nèi)置初始化節(jié)點(diǎn)

使用這個(gè)能力信不,在路由內(nèi)部默認(rèn)支持了兩個(gè)生命周期類任務(wù)纤掸,可在使用時(shí)直接引用

  • TheRouterFlowTask.APP_ONCREATE:當(dāng)Application的onCreate()執(zhí)行后初始化
  • TheRouterFlowTask.APP_ONSPLASH:當(dāng)應(yīng)用的首個(gè)Activity.onCreate()執(zhí)行后初始化

同時(shí),使用TheRouter的自動(dòng)初始化依賴浑塞,也無需擔(dān)心循環(huán)依賴造成的問題借跪,框架會(huì)在編譯期構(gòu)建有向無環(huán)圖,監(jiān)測循環(huán)依賴情況酌壕,如果發(fā)現(xiàn)會(huì)在編譯期直接報(bào)錯(cuò)掏愁,并且還會(huì)將發(fā)生循環(huán)引用的任務(wù)顯示出來,用于排錯(cuò)卵牍。

5.3 實(shí)現(xiàn)原理

每個(gè)加了 @FlowTask 注解的方法果港,都會(huì)在編譯期被解析,生成一個(gè)對(duì)應(yīng)的 Task 對(duì)象糊昙,這個(gè)對(duì)象包含了初始化方法的相關(guān)信息辛掠,比如:是否異步執(zhí)行、任務(wù)名释牺、是否依賴其他任務(wù)先執(zhí)行萝衩。

當(dāng)所有aar都編譯完成,生成好全部的 Task 以后没咙,會(huì)在主 app 中通過Gradle插件進(jìn)行聚合猩谊,在這時(shí)會(huì)將所有的 Task 做一次檢查,通過構(gòu)建有向無環(huán)圖來防止 Task 發(fā)生循環(huán)引用的情況祭刚。

每次應(yīng)用啟動(dòng)后牌捷,會(huì)在路由初始化時(shí),將有向圖中的全部Task涡驮,按照依賴關(guān)系按順序加載暗甥。

4.png

六、動(dòng)態(tài)化能力 ActionManager 的設(shè)計(jì)

Action 本質(zhì)是一個(gè)全局的系統(tǒng)回調(diào)捉捅,主要用于預(yù)埋的一系列操作撤防,例如:彈窗、上傳日志锯梁、清理緩存即碗。
與 Android 系統(tǒng)自帶的廣播通知類似焰情,你可以在任何地方聲明動(dòng)作與處理方式陌凳。并且所有Action都是可以被跟蹤的,只要你愿意内舟,可以在日志中將所有的動(dòng)作調(diào)用棧輸出合敦,以方便調(diào)試使用,這樣在一定程度上可以解決觀察者模式帶來的通惭橛巍:無法追蹤Observable的問題充岛。

6.1 Action 使用

聲明一個(gè) Action:

// action建議遵循一定的格式
const val ACTION = "therouter://action/xxx"

@FlowTask(taskName="action_demo")
fun init(context: Context) =
    TheRouter.addActionInterceptor(ACTION, object: ActionInterceptor() {
        override fun handle(context: Context, args: Bundle): Boolean {
            // do something
            return false
        }
    })

執(zhí)行一個(gè) Action:

// action建議遵循一定的格式
const val ACTION = "therouter://action/xxx"

// 如果執(zhí)行了一個(gè)沒有被聲明的Action保檐,則不會(huì)有任何動(dòng)作
TheRouter.build(ACTION).action()

6.2 高級(jí)用法

每個(gè)Action 允許關(guān)聯(lián)多個(gè) ActionInterceptor進(jìn)行處理,多個(gè)ActionInterceptor之間可以自定義攔截器優(yōu)先級(jí)崔梗,同時(shí)允許終止接下來的低優(yōu)先級(jí)攔截器的執(zhí)行夜只。

最典型應(yīng)用場景:首頁可能會(huì)有多個(gè)彈窗,不同業(yè)務(wù)之間的彈窗是有優(yōu)先級(jí)之分的蒜魄,為了體驗(yàn)優(yōu)化我們肯定不會(huì)在首頁一次把所有彈窗全部彈出扔亥,可以通過ActionInterceptor為每個(gè)彈窗聲明好優(yōu)先級(jí)關(guān)系,假設(shè)需求是首頁只能彈出3個(gè)彈窗谈为,那么第三個(gè)彈窗處理完畢即可關(guān)閉當(dāng)前事件旅挤,接下來的攔截器將不會(huì)被響應(yīng)。

abstract class ActionInterceptor {

    abstract fun handle(context: Context, args: Bundle): Boolean

    fun onFinish() {}

    /**
     * 數(shù)字越大伞鲫,優(yōu)先級(jí)越高
     */
    open val priority: Int
        get() = 5
}

6.3 客戶端動(dòng)態(tài)響應(yīng)使用場景

如果僅客戶端使用粘茄,常用的場景可能是:當(dāng)用戶執(zhí)行某些操作(打開某個(gè)頁面、H5點(diǎn)擊某個(gè)按鈕秕脓、動(dòng)態(tài)頁面配置的點(diǎn)擊事件)時(shí)柒瓣,將會(huì)自動(dòng)觸發(fā),執(zhí)行預(yù)埋的 Action 邏輯吠架。

如果與服務(wù)端鏈路打通嘹朗,這個(gè)能力其實(shí)是需要整個(gè)公司的配合,比如有一套類似智慧大腦的方案诵肛,可以基于客戶端過去的一些埋點(diǎn)數(shù)據(jù)屹培,智能推斷出用戶下一步要做的事情,然后通過長連接直接向客戶端下發(fā)指令做某些事情怔檩。那么通過客戶端預(yù)埋的頁面跳轉(zhuǎn)褪秀、彈窗、清緩存薛训、退出登錄等等操作媒吗,就可以通過服務(wù)端指令進(jìn)行操作,則就是一套完整的動(dòng)態(tài)化方案乙埃。

5.png

七闸英、一鍵切換源碼與 AAR

7.1 模塊化支持的 Gradle 腳本

在模塊化開發(fā)過程中,如果沒有采用分倉介袜,或采用了分倉但依然使用 git-submodule 的方式開發(fā)甫何,應(yīng)該都會(huì)遇到一個(gè)問題。如果集成包采用源碼編譯遇伞,構(gòu)建時(shí)間實(shí)在太久辙喂,大大降低開發(fā)調(diào)試效率;如果采用aar依賴編譯,對(duì)于底層模塊修改了代碼巍耗,每次都要重新構(gòu)建aar秋麸,在上層模塊修改版本號(hào)以后,才能繼續(xù)整包構(gòu)建編譯炬太,也極大影響開發(fā)效率灸蟆。
TheRouter 中提供了一個(gè) Gradle 腳本,只需要在開發(fā)本地的local.properties文件中聲明要參與編譯的module亲族,其他未聲明的默認(rèn)使用aar編譯次乓,這樣就能靈活切換源碼與aar,并且不會(huì)影響其他人孽水,如下節(jié)選代碼可供參考使用:

/**
 * 如果工程中有源碼票腰,則依賴源碼,否則依賴aar
 */
def moduleApi(String compileStr, Closure configureClosure) {
    String[] temp = compileStr.split(":")
    String group = temp[0]
    String artifactid = temp[1]
    String version = temp[2]

    Set<String> includeModule = new HashSet<>()
    rootProject.getAllprojects().each {
        if (it != rootProject) includeModule.add(it.name)
    }

    if (includeModule.contains(artifactid)) {
        println(project.name + "源碼依賴:===project(\":$artifactid\")")
        projects.project.dependencies.add("api", project(':' + artifactid), configureClosure)
//        projects.project.configurations { compile.exclude group: group, module: artifactid }
    } else {
        println(project.name + "依賴:=======$group:$artifactid:$version")
        projects.project.dependencies.add("api", "$group:$artifactid:$version", configureClosure)
    }
}

在實(shí)際使用時(shí)女气,可以完全使用moduleApi 替換掉原有的api杏慰。當(dāng)然, implementation也可以有一個(gè)對(duì)應(yīng)的moduleImplementation炼鞠,這樣只需要注釋或解注釋setting.gradle文件內(nèi)的include語句就可以達(dá)到切換源碼缘滥、aar的目的了。

八谒主、從其他路由遷移至 TheRouter

8.1 遷移工具一鍵遷移

TheRouter提供了圖形化界面的遷移工具朝扼,可以一鍵從其他路由遷移到TheRouter,目前僅支持ARouter霎肯,其他路由框架遷移也在開發(fā)中(GitHub下載擎颖,70M左右,請(qǐng)耐心等待):

如果項(xiàng)目中使用了ARouter的IProvider.init()方法观游,可能需要手動(dòng)處理初始化邏輯搂捧。
如下圖:

6.png

8.2 與其他路由對(duì)比

功能 TheRouter ARouter WMRouter
Fragment路由 ?? ?? ??
支持依賴注入 ?? ?? ??
加載路由表 無運(yùn)行時(shí)掃描
無反射
運(yùn)行時(shí)掃描dex
反射實(shí)例類
性能損耗大
運(yùn)行時(shí)讀文件
反射實(shí)例類
性能損耗中
注解正則表達(dá)式 ?? ?? ??
Activity指定攔截器 ??(四大攔截器可根據(jù)業(yè)務(wù)定制) ?? ??
導(dǎo)出路由文檔 ??(路由文檔支持添加注釋描述) ?? ??
動(dòng)態(tài)注冊(cè)路由信息 ?? ?? ??
APT支持增量編譯 ?? ??(開啟文檔生成則無法增量編譯) ??
plugin支持增量編譯 ?? ?? ??
多 Path 對(duì)應(yīng)同一頁面(低成本實(shí)現(xiàn)雙端path統(tǒng)一) ?? ?? ??
遠(yuǎn)端路由表下發(fā) ?? ?? ??
支持單模塊獨(dú)立初始化 ?? ?? ??
支持使用路由打開第三方庫頁面 ?? ?? ??
支持使用路由打開第三方庫頁面 ?? ?? ??
對(duì)熱修復(fù)支持(例如tinker) ??(未改變的代碼多次構(gòu)建無變動(dòng)) ??(多次構(gòu)建apt產(chǎn)物會(huì)發(fā)生變化,生成無意義補(bǔ)丁) ??(多次構(gòu)建apt產(chǎn)物會(huì)發(fā)生變化懂缕,生成無意義補(bǔ)丁)

九允跑、總結(jié)

TheRouter 并不僅僅是一個(gè)小巧靈活的路由庫,而是一整套完整的 Android 模塊化解決方案搪柑,能夠解決幾乎全部的模塊化過程中會(huì)遇到的問題聋丝。
對(duì)于現(xiàn)有的路由框架,我們也在最大限度支持平滑遷移工碾,目前已完成ARouter的一鍵遷移工具弱睦,其他框架的遷移仍在開發(fā)中。你也可以在Github issue中提出需求倚喂,我們?cè)u(píng)估后會(huì)盡快支持每篷,也歡迎任何人提供 Pull Requests

更多問題請(qǐng)?jiān)L問:詳細(xì)溝通

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末端圈,一起剝皮案震驚了整個(gè)濱河市焦读,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舱权,老刑警劉巖矗晃,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宴倍,居然都是意外死亡张症,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門鸵贬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俗他,“玉大人,你說我怎么就攤上這事阔逼≌仔疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嗜浮,是天一觀的道長羡亩。 經(jīng)常有香客問我,道長危融,這世上最難降的妖魔是什么畏铆? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吉殃,結(jié)果婚禮上辞居,老公的妹妹穿的比我還像新娘。我一直安慰自己蛋勺,他們只是感情好速侈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迫卢,像睡著了一般倚搬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乾蛤,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天每界,我揣著相機(jī)與錄音,去河邊找鬼家卖。 笑死眨层,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的上荡。 我是一名探鬼主播趴樱,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼馒闷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叁征?” 一聲冷哼從身側(cè)響起纳账,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捺疼,沒想到半個(gè)月后疏虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啤呼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年卧秘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官扣。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翅敌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惕蹄,到底是詐尸還是另有隱情哼御,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布焊唬,位于F島的核電站恋昼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赶促。R本人自食惡果不足惜液肌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸥滨。 院中可真熱鬧嗦哆,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至橘券,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卿吐,已是汗流浹背旁舰。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗡官,地道東北人箭窜。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像衍腥,于是被迫代替她去往敵國和親磺樱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纳猫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容