背景
隨著有貨App的業(yè)務(wù)不斷迭代,功能不斷累積,原有的項(xiàng)目架構(gòu)逐漸出現(xiàn)了以下問題:業(yè)務(wù)模塊代碼邊界不清晰,耦合過重;業(yè)務(wù)代碼與通用代碼的耦合導(dǎo)致很多重復(fù)代碼;基礎(chǔ)組件庫的不完善導(dǎo)致了一些三方庫的重復(fù)。鑒于此,組件化勢在必行,一個(gè)簡易的架構(gòu)分層圖如下:

單向依賴,業(yè)務(wù)組件相互之間無依賴關(guān)系,那業(yè)務(wù)組件之間的頁面跳轉(zhuǎn)該如何解決呢?
路由
現(xiàn)在比較通用的一個(gè)解決辦法是采用路由,現(xiàn)在社區(qū)有很多開源路由框架,比如ARouter,DeepLinkDispatch,ActivityRouter,LiteRouter等,再結(jié)合有貨現(xiàn)有的業(yè)務(wù)跟組件化架構(gòu),基于以下幾點(diǎn)實(shí)現(xiàn)一個(gè)路由方案:
注解配置,通過APT在編譯時(shí)去生成各個(gè)組件的路由表
AspectJ去匯總各個(gè)組件的路由表,同樣通過APT生成輔助代碼實(shí)現(xiàn)
路由調(diào)用支持原本Bundle支持的各種數(shù)據(jù)類型,各種Activity跳轉(zhuǎn)的Flags,跳轉(zhuǎn)動(dòng)畫以及支持startActivityForResult
支持?jǐn)r截器,特別是先跳轉(zhuǎn)登錄再跳轉(zhuǎn)請(qǐng)求的頁面這種異步的場景,可以通過RxJava的Subject實(shí)現(xiàn)
Activity的參數(shù)自動(dòng)注入,類似ButterKnife,這個(gè)優(yōu)先級(jí)不高
路由后的結(jié)果回調(diào)
兼容原本App中的老跳轉(zhuǎn)規(guī)則,可以將原來的流程對(duì)接到新的路由sdk
簡單用一個(gè)流程圖表示一下,下面會(huì)具體講:

實(shí)踐
對(duì)于上面說的幾點(diǎn),我從路由使用的角度來詳細(xì)闡述一下。
- 首先整個(gè)路由sdk有三部分組成

· router是一些代碼中用到的API
· router-annotation定義了一些需要用到的注解
· router-processor定義了注解處理器
· 初始化
在Application的onCreate()里初始化路由sdk,所做的事情很簡單,只是初始化一個(gè)List,這個(gè)List負(fù)責(zé)匯總各個(gè)組件模塊中的路由。
- 路由定義

· 這個(gè)注解會(huì)在編譯時(shí)由注解處理器來解析
· 此注解支持配置多個(gè)url
· url的格式:scheme://host/path/{paramKey},url正則匹配,為了不使url的解析過于復(fù)雜,這里的param匹配只會(huì)解析成String類型,對(duì)于其余的類型則可通過Router API手動(dòng)添加
· url匹配舉例:yoho://detail/88
除了Router這個(gè)注解還需要一個(gè)RouterModule注解:

· 這個(gè)注解同樣在編譯時(shí)由注解處理器來解析,注解一個(gè)空類即可,它會(huì)生成一個(gè)類DetailModuleRouter,這個(gè)類生成在當(dāng)前組件的包名下,用來匯總該組件下所有的Router注解并放入一個(gè)List中用于匹配
· 除此之外,注解處理器還需要在DetailModuleRouter中插入一段AspectJ調(diào)用代碼,用于匯總各個(gè)組件模塊路由:

簡單解釋一下,就是將該組件的路由module類加到一個(gè)匯總所有路由module類的List中,這個(gè)List就是上面第二步所說的初始化的List,加入的地方與時(shí)機(jī)便是由AspectJ來決定,這段AspectJ注解的意思就是切入路由sdk的初始化方法,在調(diào)用該方法后的代碼中去執(zhí)行該組件的路由匯總工作,其他組件同理。這樣做主要是因?yàn)榻M件化后各個(gè)業(yè)務(wù)組件編寫代碼期間無依賴關(guān)系,只有編譯運(yùn)行后才可見([參照得到的組件化方案]),也就不能強(qiáng)引用各個(gè)組件的路由module類,而AspectJ則是在編譯期間AOP正好可以應(yīng)對(duì)這個(gè)情況,由于項(xiàng)目中之前做其他功能已經(jīng)引入了AspectJ,所以對(duì)于我們來說不是很重的選擇。
· 我們看下AspectJ后的字節(jié)碼:

-
編譯期所做的事情基本理完,下面就是正式調(diào)用Router相關(guān)的API,一個(gè)例子如下:
RouterCall代表了一次路由操作,用Builder模式去構(gòu)造,有很多putXxx方法,可以去添加Bundle支持的各種數(shù)據(jù)類型,Activity相關(guān)的Flags(如Intent.FLAG_ACTIVITY_NEW_TASK),Activity直接跳轉(zhuǎn)的動(dòng)畫等,這些數(shù)據(jù)都封裝在RouterMap這個(gè)類中,然后支持添加攔截器和routerCallback,這兒的攔截器是同步的,異步的后面會(huì)說,route有一個(gè)重載的帶requestCode的方法,如果帶入一個(gè)大于0的值,則會(huì)去調(diào)用startActivityForResult。攔截器調(diào)用鏈參考了okHttp的實(shí)現(xiàn),真正的路由跳轉(zhuǎn)操作也當(dāng)成一個(gè)攔截器,放到所有攔截器的最后。下面是這個(gè)過程的一個(gè)簡單時(shí)序圖:

- 異步攔截器
2· 并沒有放到路由sdk中,因?yàn)楦鷺I(yè)務(wù)有一些耦合
3· 我這里用RxJava的PublishSubject來實(shí)現(xiàn),Subject屬于Hot Observable,有點(diǎn)類似EventBus,但是可以當(dāng)成一個(gè)專用的EventBus
4· 一個(gè)簡單例子如下:

如果需要登錄則先路由到登錄,登錄成功后發(fā)射一下:sUserPublishSubject.onNext(sUser)則會(huì)跳轉(zhuǎn)到原先的路由頁面,要注意及時(shí)的dispose,否則界面會(huì)亂跳。
總結(jié)
上面列出了路由實(shí)踐中重要的一些點(diǎn),還有些未實(shí)現(xiàn)比如支持方法調(diào)用,安全考慮,會(huì)逐步完善。為方便開發(fā),形成一份路由url映射的文檔很有必要,這個(gè)也可以在編譯時(shí)去生成,可以參考DeepLinkDispatch,不再贅述。
參考文獻(xiàn)
感謝社區(qū)優(yōu)秀的路由庫和相關(guān)文章:
· http://m.itdecent.cn/p/8a3eeeaf01e8
