美團外賣團隊開發(fā)的一款A(yù)ndroid路由框架,基于組件化的設(shè)計思路。主要提供路由、ServiceLoader兩大功能。之前美團技術(shù)博客也發(fā)表過一篇WMRouter的介紹:《WMRouter:美團外賣Android開源路由框架》。WMRouter提供了實現(xiàn)組件化的兩大基礎(chǔ)設(shè)施框架:路由和組件間接口調(diào)用。支持和文檔也很充分,可以考慮作為我們團隊實現(xiàn)組件化的基礎(chǔ)設(shè)施。
組件化方案
組件化基礎(chǔ)框架
在前期的調(diào)研工作中,我們發(fā)現(xiàn)外賣團隊的WMRouter是一個不錯的選擇。首先,WMRouter提供了路由+ServiceLoader兩大組件間通信功能,其次,WMRouter架構(gòu)清晰,擴展性比較好,并且文檔和支持也比較完備。所以我們決定了使用WMRouter作為組件化基礎(chǔ)設(shè)施框架之一。然而,直接使用WMRouter有兩個問題:
- 我們的項目已經(jīng)在使用一個路由框架,如果使用WMRouter,需要把之前使用的路由框架改成WMRouter路由框架。
- WMRouter沒有消息總線框架,我們調(diào)研的其他項目也沒有適合我們項目的消息總線框架,因此我們需要開發(fā)一個能夠滿足我們需求的消息總線框架,這部分會在后面詳細描述。
組件化分層結(jié)構(gòu)
在參考了不同的組件化方案之后,我們采用了如下分層結(jié)構(gòu):
- App殼工程:負責管理各個業(yè)務(wù)組件和打包APK,沒有具體的業(yè)務(wù)功能。
- 業(yè)務(wù)組件層:根據(jù)不同的業(yè)務(wù)構(gòu)成獨立的業(yè)務(wù)組件,其中每個業(yè)務(wù)組件包含一個Export Module和Implement Module。
- 功能組件層:對上層提供基礎(chǔ)功能服務(wù),如登錄服務(wù)、打印服務(wù)、日志服務(wù)等。
- 組件基礎(chǔ)設(shè)施:包括WMRouter,提供頁面路由服務(wù)和ServiceLoader接口調(diào)用服務(wù),以及后面會介紹的組件消息總線框架:modular-event。
整體架構(gòu)如下圖所示:
分層結(jié)構(gòu)
業(yè)務(wù)組件拆分
我們調(diào)研其他組件化方案的時候,發(fā)現(xiàn)很多組件方案都是把一個業(yè)務(wù)模塊拆分成一個獨立的業(yè)務(wù)組件,也就是拆分成一個獨立的Module。而在我們的方案中,每個業(yè)務(wù)組件都拆分成了一個Export Module和Implement Module,為什么要這樣做呢?
1. 避免循環(huán)依賴
如果采用一個業(yè)務(wù)組件一個Module的方式,如果Module A需要調(diào)用Module B提供的接口,那么Module A就需要依賴Module。同時,如果Module B需要調(diào)用Module A的接口,那么Module B就需要依賴Module A。此時就會形成一個循環(huán)依賴,這是不允許的。
也許有些讀者會說,這個好解決:可以把Module A和Module B要依賴的接口放到另一個Module中去,然后讓Module A和Module B都去依賴這個Module就可以了。這確實是一個解決辦法,并且有些項目組在使用這種把接口下沉的方法。
但是我們希望一個組件的接口,是由這個組件自己提供,而不是放在一個更加下沉的接口里面,所以我們采用了把每個業(yè)務(wù)組件都拆分成了一個Export Module和Implement Module。這樣的話,如果Module A需要調(diào)用Module B提供的接口,同時Module B需要調(diào)用Module A的接口,只需要Module A依賴Module B Export,Module B依賴Module A Export就可以了。
組件結(jié)構(gòu)
2. 業(yè)務(wù)組件完全平等
在使用單Module方案的組件化方案中,這些業(yè)務(wù)組件其實不是完全平等,有些被依賴的組件在層級上要更下沉一些。但是采用Export Module+Implement Module的方案,所有業(yè)務(wù)組件在層級上完全平等。
3. 功能劃分更加清晰
每個業(yè)務(wù)組件都劃分成了Export Module+Implement Module的模式,這個時候每個Module的功能劃分也更加清晰。Export Module主要定義組件需要對外暴露的部分,主要包含:
- 對外暴露的接口,這些接口用WMRouter的ServiceLoader進行調(diào)用。
- 對外暴露的事件,這些事件利用消息總線框架modular-event進行訂閱和分發(fā)。
- 組件的Router Path,組件化之前的工程雖然也使用了Router框架,但是所有Router Path都是定義在了一個下沉Module的公有Class中。這樣導(dǎo)致的問題是,無論哪個模塊添加/刪除頁面,或是修改路由,都需要去修改這個公有的Class。設(shè)想如果組件化拆分之后,某個組件新增了頁面,還要去一個外部的Java文件中新增路由,這顯然難以接受,也不符合組件化內(nèi)聚的目標。因此,我們把每個組件的Router Path放在組件的Export Module中,既可以暴露給其他組件,也可以做到每個組件管理自己的Router Path,不會出現(xiàn)所有組件去修改一個Java文件的窘境。
Implement Module是組件實現(xiàn)的部分,主要包含:
- 頁面相關(guān)的Activity、Fragment,并且用WMRouter的注解定義路由。
- Export Module中對外暴露的接口的實現(xiàn)。
- 其他的業(yè)務(wù)邏輯。
組件功能劃分
組件功能劃分
組件化消息總線框架modular-event
前文提到的實現(xiàn)組件化基礎(chǔ)設(shè)施框架中,我們用外賣團隊的WMRouter實現(xiàn)頁面路由和組件間接口調(diào)用,但是卻沒有消息總線的基礎(chǔ)框架,因此,我們自己開發(fā)了一個組件化消息總線框架modular-event。
為什么需要消息總線框架
既然已經(jīng)有了ServiceLoader這種組件間接口調(diào)用的框架,為什么還需要消息總線這種方式呢?主要有兩個理由。
1. 更進一步的解耦
基于接口調(diào)用的ServiceLoader框架的確實現(xiàn)了解耦,但是消息總線能夠?qū)崿F(xiàn)更徹底的解耦。接口調(diào)用的方式調(diào)用方需要依賴這個接口并且知道哪個組件實現(xiàn)了這個接口。消息總線方式發(fā)送者只需要發(fā)送一個消息,根本不用關(guān)心是否有人訂閱這個消息,這樣發(fā)送者根本不需要了解其他組件的情況,和其他組件的耦合也就越少。
2. 多對多的通信
基于接口的方式只能進行一對一的調(diào)用,基于消息總線的方式能夠提供多對多的通信。
消息總線的優(yōu)點和缺點
總的來說,消息總線最大的優(yōu)點就是解耦,因此很適合組件化這種需要對組件間進行徹底解耦的場景。然而,消息總線被很多人詬病的重要原因,也確實是因為消息總線容易被濫用。消息總線容易被濫用一般體現(xiàn)在幾個場景:
1. 消息難以溯源
有時候我們在閱讀代碼的過程中,找到一個訂閱消息的地方,想要看看是誰發(fā)送了這個消息,這個時候往往只能通過查找消息的方式去“溯源”。導(dǎo)致我們在閱讀代碼,梳理邏輯的過程不太連貫,有種被割裂的感覺。
2. 消息發(fā)送比較隨意,沒有強制的約束
消息總線在發(fā)送消息的時候一般沒有強制的約束。無論是EventBus、RxBus或是LiveDataBus,在發(fā)送消息的時候既沒有對消息進行檢查,也沒有對發(fā)送調(diào)用進行約束。這種不規(guī)范性在特定的時刻,甚至?xí)頌?zāi)難性的后果。比如訂閱方訂閱了一個名為login_success的消息,編寫發(fā)送消息的是一個比較隨意的程序員,沒有把這個消息定義成全局變量,而是定義了一個臨時變量String發(fā)送這個消息。不幸的是,他把消息名稱login_success拼寫成了login_seccess。這樣的話,訂閱方永遠接收不到登錄成功的消息,而且這個錯誤也很難被發(fā)現(xiàn)。
組件化消息總線的設(shè)計目標
1. 消息由組件自己定義
以前我們在使用消息總線時,喜歡把所有的消息都定義到一個公共的Java文件里面。但是組件化如果也采用這種方案的話,一旦某個組件的消息發(fā)生變動,都會去修改這個Java文件。所以我們希望由組件自己來定義和維護消息定義文件。
2. 區(qū)分不同組件定義的同名消息
如果消息由組件定義和維護,那么有可能不同組件定義了重名的消息,消息總線框架需要能夠區(qū)分這種消息。
3. 解決前文提到的消息總線的缺點
解決消息總線消息難以溯源和消息發(fā)送沒有約束的問題。
基于LiveData的消息總線
組件化消息總線框架modular-event基于LiveData構(gòu)建,使用LiveData構(gòu)建消息總線有很多優(yōu)點:
- 使用LiveData構(gòu)建消息總線具有生命周期感知能力,使用者不需要調(diào)用反注冊,相比EventBus和RxBus使用更為方便,并且沒有內(nèi)存泄漏風險。
- 使用普通消息總線,如果回調(diào)的時候Activity處于Stop狀態(tài),這個時候進行彈Dialog一類的操作就會引起崩潰。使用LiveData構(gòu)建消息總線完全沒有這個風險。
組件消息總線modular-event的實現(xiàn)
解決不同組件定義了重名消息的問題
其實這個問題還是比較好解決的,實現(xiàn)的方式就是采用兩級HashMap的方式解決。第一級HashMap的構(gòu)建以ModuleName作為Key,第二級HashMap作為Value;第二級HashMap以消息名稱EventName作為Key,LiveData作為Value。查找的時候先用組件名稱ModuleName在第一級HashMap中查找,如果找到則用消息名EventName在第二級HashName中查找。整個結(jié)構(gòu)如下圖所示:
消息總線結(jié)構(gòu)
對消息總線的約束
我們希望消息總線框架有以下約束:
- 只能訂閱和發(fā)送在組件中預(yù)定義的消息。換句話說,使用者不能發(fā)送和訂閱臨時消息。
- 消息的類型需要在定義的時候指定。
- 定義消息的時候需要指定屬于哪個組件。
如何實現(xiàn)這些約束
- 在消息定義文件上使用注解,定義消息的類型和消息所屬Module。
- 定義注解處理器,在編譯期間收集消息的相關(guān)信息。
- 在編譯器根據(jù)消息的信息生成調(diào)用時需要的interface,用接口約束消息發(fā)送和訂閱。
- 運行時構(gòu)建基于兩級HashMap的LiveData存儲結(jié)構(gòu)。
- 運行時采用interface+動態(tài)代理的方式實現(xiàn)真正的消息訂閱和發(fā)送。
整個流程如下圖所示:
消息總線modular-event的結(jié)構(gòu)
- modular-event-base:定義Anotation及其他基本類型
- modular-event-core:modular-event核心實現(xiàn)
- modular-event-compiler:注解處理器
- modular-event-plugin:Gradle Plugin
Anotation
- @ModuleEvents:消息定義
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ModuleEvents {
String module() default “”;
}
- @EventType:消息類型
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface EventType {
Class value();
}
消息定義
通過@ModuleEvents注解一個定義消息的Java類,如果@ModuleEvents指定了屬性module,那么這個module的值就是這個消息所屬的Module,如果沒有指定屬性module,則會把定義消息的Java類所在的包的包名作為消息所屬的Module。
在這個消息定義java類中定義的消息都是public static final String類型??梢酝ㄟ^@EventType指定消息的類型,@EventType支持java原生類型或自定義類型,如果沒有用@EventType指定消息類型,那么消息的類型默認為Object,下面是一個消息定義的示例:
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現(xiàn)在。
深知大多數(shù)Android工程師,想要提升技能,往往是自己摸索成長或者是報班學(xué)習(xí),但對于培訓(xùn)機構(gòu)動則幾千的學(xué)費,著實壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年Android移動開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時減輕大家的負擔。
既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗的小伙伴深入學(xué)習(xí)提升的進階課程,基本涵蓋了95%以上Android開發(fā)知識點,真正體系化!
由于文件比較大,這里只是將部分目錄大綱截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且后續(xù)會持續(xù)更新文章來源:http://www.zghlxwxcb.cn/news/detail-855338.html
如果你覺得這些內(nèi)容對你有幫助,可以添加V獲?。簐ip204888 (備注Android)
程,基本涵蓋了95%以上Android開發(fā)知識點,真正體系化!**
由于文件比較大,這里只是將部分目錄大綱截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且后續(xù)會持續(xù)更新
如果你覺得這些內(nèi)容對你有幫助,可以添加V獲?。簐ip204888 (備注Android)
[外鏈圖片轉(zhuǎn)存中…(img-MoG1yImc-1711634944690)]文章來源地址http://www.zghlxwxcb.cn/news/detail-855338.html
到了這里,關(guān)于Android組件化方案及組件消息總線modular-event實戰(zhàn),渣本Android開發(fā)小伙如何一步步成為架構(gòu)師的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!