Android組件化開發(fā)原理

之前寫過一篇關(guān)于Android組件化的文章,《Android組件化框架設(shè)計(jì)與實(shí)踐》,之前沒看過的小伙伴可以先點(diǎn)擊閱讀。那篇文章是從實(shí)戰(zhàn)中進(jìn)行總結(jié)得來,是公司的一個真實(shí)項(xiàng)目進(jìn)行組件化架構(gòu)改造,粒度會分的更粗些,是對整體架構(gòu)實(shí)踐進(jìn)行相應(yīng)的總結(jié),里面說了要打造一個組件化框架的話,需要從以下7個方面入手:

代碼解耦。如何將一個龐大的工程分成有機(jī)的整體?

組件單獨(dú)運(yùn)行。因?yàn)槊總€組件都是高度內(nèi)聚的,是一個完整的整體,如何讓其單獨(dú)運(yùn)行和調(diào)試?

組件間通信。由于每個組件具體實(shí)現(xiàn)細(xì)節(jié)都互相不了解,但每個組件都需要給其他調(diào)用方提供服務(wù),那么主項(xiàng)目與組件、組件與組件之間如何通信就變成關(guān)鍵?

UI 跳轉(zhuǎn)。UI 跳轉(zhuǎn)指的是特殊的數(shù)據(jù)傳遞,跟組件間通信區(qū)別有什么不同?

組件生命周期。這里的生命周期指的是組件在應(yīng)用中存在的時間,組件是否可以做到按需、動態(tài)使用、因此就會涉及到組件加載、卸載等管理問題。

集成調(diào)試。在開發(fā)階段如何做到按需編譯組件?一次調(diào)試中可能有一兩個組件參與集成,這樣編譯時間就會大大降低,提高開發(fā)效率。

代碼隔離。組件之間的交互如果還是直接引用的話,那么組件之間根本沒有做到解耦,如何從根本上避免組件之間的直接引用,也就是如何從根本上杜絕耦合的產(chǎn)生?

今天則會從更小細(xì)粒度入手,主要講講在組件化架構(gòu)下組件與組件之間通信機(jī)制是如何、包括所謂的UI跳轉(zhuǎn),其實(shí)也是組件化通信,只不過它稍微特殊點(diǎn),單獨(dú)抽取出來而已。學(xué)習(xí)知識的過程很常見的一個思路就是從整體概況入手,首先對整體有個粗略的印象,然后再深入細(xì)節(jié),抽絲剝繭般去挖掘其中的內(nèi)在原理,一個點(diǎn)一個不斷去突破,這樣就能建立起自己整個知識樹,所以今天我們就從通信機(jī)制這個點(diǎn)入手,看看其中內(nèi)在玄機(jī)有哪些。

思維導(dǎo)圖

同樣,在每寫一篇文章之前,放個思維導(dǎo)圖,這樣做的好處對于想寫的內(nèi)容有很好的梳理,邏輯和結(jié)構(gòu)上顯得清晰點(diǎn)。

主流方式

總所周知,Android提供了很多不同的信息的傳遞方式,比如在四大組件中本地廣播、進(jìn)程間的AIDL、匿名間的內(nèi)存共享、Intent Bundle傳遞等等,那么在這么多傳遞方式,哪種類型是比較適合組件與組件直接的傳遞呢。

本地廣播,也就是LoacalBroadcastRecevier。更多是用在同一個應(yīng)用內(nèi)的不同系統(tǒng)規(guī)定的組件進(jìn)行通信,好處在于:發(fā)送的廣播只會在自己的APP內(nèi)傳播,不會泄漏給其他的APP,其他APP無法向自己的APP發(fā)送廣播,不用被其他APP干擾。本地廣播好比對講通信,成本低,效率高,但有個缺點(diǎn)就是兩者通信機(jī)制全部委托與系統(tǒng)負(fù)責(zé),我們無法干預(yù)傳輸途中的任何步驟,不可控制,一般在組件化通信過程中采用比例不高。

進(jìn)程間的AIDL。這個粒度在于進(jìn)程,而我們組件化通信過程往往是在線程中,況且AIDL通信也是屬于系統(tǒng)級通信,底層以Binder機(jī)制,雖說Android提供模板供我們實(shí)現(xiàn),但往往使用者不好理解,交互比較復(fù)雜,往往也不適用應(yīng)用于組件化通信過程中。

匿名的內(nèi)存共享。比如用Sharedpreferences,在處于多線程場景下,往往會線程不安全,這種更多是存儲一一些變化很少的信息,比如說組件里的配置信息等等。

Intent Bundle傳遞。包括顯性和隱性傳遞,顯性傳遞需要明確包名路徑,組件與組件往往是需要互相依賴,這背離組件化中SOP(關(guān)注點(diǎn)分離原則),如果走隱性的話,不僅包名路徑不能重復(fù),需要定義一套規(guī)則,只有一個包名路徑出錯,排查起來也稍顯麻煩,這個方式往往在組件間內(nèi)部傳遞會比較合適,組件外與其他組件打交道則使用場景不多。

說了這么多,那組件化通信什么機(jī)制比較適合呢?既然組件層中的模塊是相互獨(dú)立的,它們之間并不存在任何依賴。沒有依賴就無法產(chǎn)生關(guān)系,沒有關(guān)系,就無法傳遞消息,那要如何才能完成這種交流?

目前主流做法之一就是引入第三者,比如圖中的Base Module。



組件層的模塊都依賴于基礎(chǔ)層,從而產(chǎn)生第三者聯(lián)系,這種第三者聯(lián)系最終會編譯在APP Module中,那時將不會有這種隔閡,那么其中的Base Module就是跨越組件化層級的關(guān)鍵,也是模塊間信息交流的基礎(chǔ)。比較有代表性的組件化開源框架有得到DDComponentForAndroid、阿里Arouter、聚美Router 等等。

除了這種以通過引入第三者方式,還有一種解決方式是以事件總線方式,但這種方式目前開源的框架中使用比例不高,如圖:



事件總線通過記錄對象,使用監(jiān)聽者模式來通知對象各種事件,比如在現(xiàn)實(shí)生活中,我們要去找房子,一般都去看小區(qū)的公告欄,因?yàn)槟沁厱?jīng)常發(fā)布一些出租信息,我們?nèi)ゲ榭吹倪^程中就形成了訂閱的關(guān)系,只不過這種是被動去訂閱,因?yàn)橹挥凶约盒枰曳孔恿瞬湃タ?,平時一般不會去看。小區(qū)中的公告欄可以想象成一個事件總線發(fā)布點(diǎn),監(jiān)聽者則是哪些想要找房子的人,當(dāng)有房東在公告欄上貼上出租房信息時,如果公告欄有訂閱信息功能,比如引入門衛(wèi)保安,已經(jīng)把之前來這個公告欄要查看的找房子人一一進(jìn)行電話登記,那么一旦有新出租消息產(chǎn)生,則門衛(wèi)會把這條消息一一進(jìn)行短信群發(fā),那么找房子人則會收到這條消息進(jìn)行后續(xù)的操作,是馬上過來看,還是延遲過來,則根據(jù)自己的實(shí)際情況進(jìn)行處理。在目前開源庫中,有EventBus、RxBus就是采用這種發(fā)布/訂閱模式,優(yōu)點(diǎn)是簡化了Android組件之間的通信方式,實(shí)現(xiàn)解耦,讓業(yè)務(wù)代碼更加簡潔,可以動態(tài)設(shè)置事件處理線程和優(yōu)先級,缺點(diǎn)則是每個事件需要維護(hù)一個事件類,造成事件類太多,無形中加大了維護(hù)成本。那么在組件化開源框架中有ModuleBus、CC 等等。

這兩者模式更詳細(xì)的對比,可以查看這篇文章多個維度對比一些有代表性的開源android組件化開發(fā)方案

實(shí)現(xiàn)方案

事件總線,又可以叫做組件總線,路由+接口,則相對好理解點(diǎn),今天從閱讀它們框架源碼,我們來對比這兩種實(shí)現(xiàn)方案的不同之處。

組件總線

這邊選取的是ModuleBus框架,這個方案特別之處在于其借鑒了EventBus的思想,組件的注冊/注銷和組件調(diào)用的事件發(fā)送都跟EventBus類似,能夠傳遞一些基礎(chǔ)類型的數(shù)據(jù),而并不需要在Base Moudel中添加額外的類。所以不會影響B(tài)ase模塊的架構(gòu),但是無法動態(tài)移除信息接收端的代碼,而自定義的事件信息類型還是需要添加到Base Module中才能讓其他功能模塊索引。

/**?????*?Object?methodClass?????*?String?methodName;?????*?MethodInfo?method?info?????*/????

private?static?ArrayMap<Object,ArrayMap<String,MethodInfo>>?moduleEventMethods?=?new?ArrayMap<();????

/**?????*?Class?IBaseClient.class?????*?String?methodName?????*?Object?methodClass?????*/???

?private?static?ArrayMap<Class<?>,ArrayMap<String,ArrayList<Object>>>?moduleMethodClient?=?new?ArrayMap<>();

在使用方法上,在onCreate()和onDestroy()中需要注冊和解綁,比如

ModuleBus.getInstance().register(this);

ModuleBus.getInstance().unregister(this);

最終使用類似EventBus 中 post 方法一樣,進(jìn)行兩個組件間的通信。這個框架的封裝的post 方法如下

可以看到,它是通過遍歷之前內(nèi)部的ArrayMap,把注冊在里面的方法找出,根據(jù)傳入的參數(shù)進(jìn)行匹配,使用反射調(diào)用。

接口+路由

接口+路由實(shí)現(xiàn)方式則相對容易理解點(diǎn),我之前實(shí)踐的一個項(xiàng)目就是通過這種方式實(shí)現(xiàn)的。具體地址如下:DemoComponent實(shí)現(xiàn)思路是專門抽取一個LibModule作為路由服務(wù),每個組件聲明自己提供的服務(wù) Service API,這些 Service 都是一些接口,組件負(fù)責(zé)將這些 Service 實(shí)現(xiàn)并注冊到一個統(tǒng)一的路由 Router 中去,如果要使用某個組件的功能,只需要向Router 請求這個 Service 的實(shí)現(xiàn),具體的實(shí)現(xiàn)細(xì)節(jié)我們?nèi)徊魂P(guān)心,只要能返回我們需要的結(jié)果就可以了。

比如定義兩個路由地址,一個登陸組件,一個設(shè)置組件,核心代碼:

public?class?RouterPath?{???

?//注意路由的命名,路徑第一個開頭需要不一致,保證唯一性????

//Login?Service????public?static?final?String?ROUTER_PATH_TO_LOGIN_SERVICE?=?"/login/service";????//Setting?Service????

public?static?final?String?ROUTER_PATH_TO_SETTING_SERVICE?=?"/setting/service";

}

那么就相應(yīng)著就有兩個接口API,如下:

public?interface?ILoginProvider?extends?IProvider{????

void?goToLogin(Activity?activity);

}

public?interface?ISettingProvider?extends?IProvider {????

void?goToSetting(Activity?activity);

}

(```)

? 代碼...

? 代碼...

? 代碼...

(```)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容