安居客Android项目架构演进
入職安居客三年從工程師到Team Leader,見證了Android團(tuán)隊(duì)一路走來的發(fā)展歷程。因此有心將這些記錄下來與大家分享,也算是對(duì)自己三年來一部分工作的總結(jié)。希望對(duì)大家有所幫助,更希望能得到大家寶貴的建議。
三網(wǎng)合并
三年前入職時(shí)安居客在業(yè)務(wù)上剛完成了三網(wǎng)合并(新房、二手房、好租和商業(yè)地產(chǎn)多個(gè)平臺(tái)多個(gè)網(wǎng)站合成現(xiàn)在的anjuke.com,這在公司的歷史上稱之為三網(wǎng)合并),因此app端也將原先的新房、二手房、好租和商業(yè)地產(chǎn)多個(gè)app合并成為了現(xiàn)在的安居客app。所謂的合并也差不多就是將多個(gè)項(xiàng)目的代碼拷貝到了一起組成了新的Anjuke Project。下面這張圖能更加直觀的呈現(xiàn)當(dāng)時(shí)的狀況:
這一時(shí)期代碼結(jié)構(gòu)混亂、層次不清,各業(yè)務(wù)技術(shù)方案不統(tǒng)一,冗余代碼充斥項(xiàng)目的各個(gè)角落;甚至連基本的包結(jié)構(gòu)也是胡亂不堪,項(xiàng)目架構(gòu)更是無從談起。大家只不過是不停地往上堆砌代碼添加新功能罷了。于是我進(jìn)入公司的第一件事就是向Leader申請(qǐng)梳理了整個(gè)項(xiàng)目的結(jié)構(gòu)。
而后隨著項(xiàng)目的迭代,我們不斷引入了Retrofit、UniversalImageLoader、OKHttp、ButterKnife等一系列成熟的開源庫,同時(shí)我們也開發(fā)了自己的UI組件庫UIComponent、基礎(chǔ)工具庫CommonUtils、基于第三方地圖封裝的MapSDK、即時(shí)聊天模塊ChatLibrary等等。這之后安居客項(xiàng)目架構(gòu)大致演變成了由基礎(chǔ)組件層、業(yè)務(wù)組件層和業(yè)務(wù)層組成的三層架構(gòu)。如下圖:
其中業(yè)務(wù)層是一種非標(biāo)準(zhǔn)的MVC架構(gòu),Activity和Fragment承擔(dān)了View和Controller的職責(zé):
前面這種分層的架構(gòu)本身是沒太大問題的,即使到了現(xiàn)在我們的業(yè)務(wù)項(xiàng)目也已然是基于這種分層的架構(gòu)來構(gòu)建的,只不過在不斷的迭代中我們做了些許調(diào)整(分層架構(gòu)后面在介紹組件化和模塊化的時(shí)候會(huì)詳細(xì)介紹)。但是隨著業(yè)務(wù)的不斷迭代,我們慢慢發(fā)現(xiàn)業(yè)務(wù)層這種非標(biāo)準(zhǔn)的MVC架構(gòu)帶來了種種影響團(tuán)隊(duì)開發(fā)效率的問題:
- Activity和Fragment越來越多的同時(shí)承擔(dān)了Controller和View的職責(zé),導(dǎo)致他們變得及其臃腫且難以維護(hù);
- 由于Controller和View的揉合,導(dǎo)致單元測試起來很困難;
- 回調(diào)嵌套太多,面對(duì)負(fù)責(zé)業(yè)務(wù)時(shí)的代碼邏輯不清晰,難以理解且不利于后期維護(hù);
- 各層次模塊之間職責(zé)不清晰等等
鑒于三網(wǎng)合并時(shí)期我還未加入安居客,所以對(duì)這一塊的理解難免有偏差,如果有安居客的老同事發(fā)現(xiàn)文章中的描述有不對(duì)的地方還望批評(píng)指正。
由RxJava驅(qū)動(dòng)的MVP架構(gòu)
一種技術(shù)架構(gòu)無法滿足所有的業(yè)務(wù)項(xiàng)目,更不可能有一種架構(gòu)方案能夠一勞永逸。正如上一節(jié)中提到的隨著業(yè)務(wù)的不斷迭代,現(xiàn)有架構(gòu)的缺陷逐漸浮出水面,項(xiàng)目架構(gòu)必需不斷升級(jí)迭代才能更好地服務(wù)于業(yè)務(wù)。
MVP的設(shè)計(jì)與實(shí)現(xiàn)
在研究了Google推出的基于MVP架構(gòu)的demo后,我們發(fā)現(xiàn)MVP架構(gòu)能解決現(xiàn)在所面臨過的很多問題,于是我們學(xué)習(xí)并引入到了我們的項(xiàng)目中來,并針對(duì)性的做了部分調(diào)整。下圖呈現(xiàn)的是安居客MVP方案:
以前面提到的三層架構(gòu)的方案來看是這樣的:
基于此架構(gòu)我在GitHub上開源了一個(gè)項(xiàng)目MinimalistWeather,有興趣的小伙伴可以去clone下來看看,如果覺得對(duì)你有幫助就給個(gè)star吧。 :)
- View Layer: 只負(fù)責(zé)UI的繪制呈現(xiàn),包含F(xiàn)ragment和一些自定義的UI組件,View層需要實(shí)現(xiàn)ViewInterface接口。Activity在項(xiàng)目中不再負(fù)責(zé)View的職責(zé),僅僅是一個(gè)全局的控制者,負(fù)責(zé)創(chuàng)建View和Presenter的實(shí)例;
- Model Layer: 負(fù)責(zé)檢索、存儲(chǔ)、操作數(shù)據(jù),包括來自網(wǎng)絡(luò)、數(shù)據(jù)庫、磁盤文件和SharedPreferences的數(shù)據(jù);
- Presenter Layer: 作為View Layer和Module Layer的之間的紐帶,它從model層中獲取數(shù)據(jù),然后調(diào)用View的接口去控制View;
- Contract: 我們參照Google的demo加入契約類Contract來統(tǒng)一管理View和Presenter的接口,使得某一功能模塊的接口能更加直觀的呈現(xiàn)出來,這樣做是有利于后期維護(hù)的。
另外這套MVP架構(gòu)還為我們帶來了一個(gè)額外的好處:我們有了足夠明確的開發(fā)規(guī)范和標(biāo)準(zhǔn)。細(xì)致到了每一個(gè)類應(yīng)該放到哪個(gè)包下,哪個(gè)類具體應(yīng)該負(fù)責(zé)什么職責(zé)等等。這對(duì)于我們的Code Review、接手他人的功能模塊等都提供了極大的便利。前面提到的MinimalistWeather就是為了定規(guī)范定標(biāo)準(zhǔn)而開發(fā)的。
這一時(shí)期我們還在項(xiàng)目中引入了RxJava,很好的解決了前面提到的嵌套回調(diào)的問題,同時(shí)能夠幫助我們簡化復(fù)雜業(yè)務(wù)場景下的代碼邏輯(當(dāng)然RxJava的好處遠(yuǎn)遠(yuǎn)不止這么一點(diǎn),對(duì)RxJava不了解的同學(xué)可以去翻翻我之前一系列關(guān)于RxJava的文章)。我們也將網(wǎng)絡(luò)庫升級(jí)到了Retrofit2+OKHttp3,它們和RxJava之間能更好的配合。
MVP帶來的新問題及解決方案
是不是升級(jí)到了MVP架構(gòu)就高枕無憂了呢?很明顯不是這樣!MVP架構(gòu)也會(huì)帶來以下新的問題:
- 由于大量的業(yè)務(wù)邏輯處理轉(zhuǎn)移到了Presenter層,在一些復(fù)雜的業(yè)務(wù)場景中Presenter同樣會(huì)變得臃腫難懂。細(xì)心的同學(xué)可能注意到了前面的架構(gòu)圖中的Model層有個(gè)Data Repository模塊,Data Repository在這里有兩個(gè)作用:一是可以將原本由Presenter處理的部分邏輯轉(zhuǎn)移到這里來處理,包括數(shù)據(jù)的校驗(yàn)、部分單純只與數(shù)據(jù)相關(guān)的邏輯等等,向Presenter屏蔽數(shù)據(jù)處理細(xì)節(jié),比如作為Presenter就不必關(guān)心Model層傳遞過來的數(shù)據(jù)到底是來至網(wǎng)絡(luò)還是來至數(shù)據(jù)庫還是來至本地文件等等;二是我們引入了RxJava,但是只有網(wǎng)絡(luò)層中的Retrofit能返回Observable對(duì)象,其他模塊都是返回的還是一些非Observable的Java對(duì)象,為了能在整個(gè)Presenter層中都體驗(yàn)RxJava帶來的美妙之處,因此可以通過Data Repository做一層轉(zhuǎn)換;
- 現(xiàn)在的MVP架構(gòu)中最重的部分就是Model Layer了,這一點(diǎn)從前面的架構(gòu)圖中就能體現(xiàn)。因此這就要求我們?cè)贛odel層的設(shè)計(jì)過程中職責(zé)劃分要足夠清晰,分包更明確,耦合度更低。至于分包大家可以參考MinimalistWeather的方案:db包為數(shù)據(jù)庫模塊、http包為網(wǎng)絡(luò)模塊、preference包是對(duì)SharedPreferences的一些封裝、repository包就是前面提到的Data Repository模塊;
- 同時(shí)還有一點(diǎn)需要注意,很多人在使用RxJava的過程中往往忘記了對(duì)生命周期的管理,這很容易造成內(nèi)存泄露。MinimalistWeather中采用了CompositeSubscription來管理,你也可以使用RxLifecycle這類開源庫來管理生命周期。
組件化與模塊化
去年下半年我們Android團(tuán)隊(duì)內(nèi)部成立了技術(shù)小組,基礎(chǔ)組件的開發(fā)是技術(shù)小組很重要的一部分工作,所以組件化是我們正在做的事;模塊化更多的是現(xiàn)有的方案受到來自業(yè)務(wù)上的挑戰(zhàn)以及受到了Oasis Feng在MDCC上的分享和整個(gè)大環(huán)境的啟發(fā),現(xiàn)在正處于設(shè)計(jì)規(guī)劃和demo開發(fā)的階段。
組件化
組件化不是個(gè)新概念,通俗的講組件化就是基于可重用的目的,將一個(gè)大的軟件系統(tǒng)拆分成一個(gè)個(gè)獨(dú)立組件。
組件化的帶來的好處不言而喻:
- 避免重復(fù)造輪子,節(jié)省開發(fā)維護(hù)成本;
- 降低項(xiàng)目復(fù)雜性,提升開發(fā)效率;
- 多個(gè)團(tuán)隊(duì)公用同一個(gè)組件,在一定層度上確保了技術(shù)方案的統(tǒng)一性。
現(xiàn)在的安居客有是三個(gè)業(yè)務(wù)團(tuán)隊(duì):安居客用戶app、經(jīng)紀(jì)人app、集客家app。為了避免各個(gè)業(yè)務(wù)團(tuán)隊(duì)重復(fù)造輪子,團(tuán)隊(duì)中也需要有一定的技術(shù)沉淀,因此組件化是必須的。從本篇的第一節(jié)大家就能看到組件化的影子,只不過在這之前我們做的并不好。現(xiàn)在我們需要提供更多的、職能單一、性能更優(yōu)的組件供業(yè)務(wù)團(tuán)隊(duì)使用。根據(jù)業(yè)務(wù)相關(guān)性,我們將這些組件分為:基礎(chǔ)組件和業(yè)務(wù)組件。后面在介紹模塊化的時(shí)候會(huì)有進(jìn)一步的描述。
模塊化
自從Oasis Feng在去年的MDCC2016上分享了模塊化的經(jīng)驗(yàn)后,模塊化在Android社區(qū)越來越多的被提起。我們自然也不落俗的去做了一些研究和探索。安居客現(xiàn)在面臨很多問題:例如全量編譯時(shí)間太長(我這臺(tái)13款的MacBook Pro打一次包得花十多分鐘);例如新房、二手房、租房等等模塊間耦合嚴(yán)重,不利于多團(tuán)隊(duì)并行開發(fā)測試;另外在17年初公司重新將租房app撿起推廣,單獨(dú)讓人來開發(fā)維護(hù)一個(gè)三年前的項(xiàng)目并不劃算,所以我們希望能直接從現(xiàn)在的安居客用戶端中拆分出租房模塊作為一個(gè)單獨(dú)的app發(fā)布上線。這樣看來模塊化似乎是一個(gè)不錯(cuò)的選擇。
所以我們做模塊化的目的大致是這樣的:
- 業(yè)務(wù)模塊間解耦
- 單個(gè)業(yè)務(wù)模塊單獨(dú)編譯打包,加快編譯速度
- 多團(tuán)隊(duì)間并行開發(fā)、測試
- 解決好租App需要單獨(dú)維護(hù)的問題,降低研發(fā)成本
15年Trinea還在安居客的時(shí)候開發(fā)了一套插件化框架,但受限于當(dāng)時(shí)的團(tuán)隊(duì)規(guī)模并且插件化對(duì)整個(gè)項(xiàng)目的改造太大,因此在安居客團(tuán)隊(duì)中插件化并未實(shí)施下來。而模塊化其實(shí)是個(gè)很好的過渡方案,將項(xiàng)目按照模塊拆分后各業(yè)務(wù)模塊間解耦的問題不存在了,后續(xù)如有必要,再進(jìn)行插件化改造只不過是水到渠成的事。
來看看安居客用戶app的模塊化設(shè)計(jì)圖:
整個(gè)項(xiàng)目分為三層,從下往上分別是:
- Basic Component Layer: 基礎(chǔ)組件層,顧名思義就是一些基礎(chǔ)組件,包含了各種開源庫以及和業(yè)務(wù)無關(guān)的各種自研工具庫;
- Business Component Layer: 業(yè)務(wù)組件層,這一層的所有組件都是業(yè)務(wù)相關(guān)的,例如上圖中的支付組件AnjukePay、數(shù)據(jù)模擬組件DataSimulator等等;
- Business Module Layer: 業(yè)務(wù)module層,在Android Studio中每塊業(yè)務(wù)對(duì)應(yīng)一個(gè)單獨(dú)的module。例如安居客用戶app我們就可以拆分成新房module、二手房module、IM module等等,每個(gè)單獨(dú)的Business Module都必須準(zhǔn)遵守前面提到的MVP架構(gòu)。
同時(shí)針對(duì)模塊化我們也需要定義一些自己的游戲規(guī)則:
- 對(duì)于Business Module Layer,各業(yè)務(wù)模塊之間的通訊跳轉(zhuǎn)采用路由框架Router來實(shí)現(xiàn)(可能會(huì)采用成熟的開源庫,也可能會(huì)選擇重復(fù)造輪子);
- 對(duì)于Business Component Layer,單一業(yè)務(wù)組件只能對(duì)應(yīng)某一項(xiàng)具體的業(yè)務(wù),對(duì)于有個(gè)性化需求的對(duì)外部提供接口讓調(diào)用方定制;
- 合理控制各組件和各業(yè)務(wù)模塊的拆分粒度,太小的公有模塊不足以構(gòu)成單獨(dú)組件或者模塊的,我們先放到類似于CommonBusuness的組件中,在后期不斷的重構(gòu)迭代中視情況進(jìn)行進(jìn)一步的拆分(這一點(diǎn)的靈感來源于Trinea的文章);
- 上層的公有的業(yè)務(wù)或者功能模塊可以逐步下放到下層,合理把握好度就好;
- 各Layer間嚴(yán)禁反向依賴,橫向依賴關(guān)系由各業(yè)務(wù)Leader和技術(shù)小組商討決定。
對(duì)于模塊化項(xiàng)目,每個(gè)單獨(dú)的business module都可以單獨(dú)編譯成APK。在開發(fā)階段需要單獨(dú)打包編譯,項(xiàng)目發(fā)布的時(shí)候又需要它作為項(xiàng)目的一個(gè)module來整體編譯打包。簡單的說就是開發(fā)時(shí)是application,發(fā)布時(shí)是library。因此需要你在business module的gradle配置文件中加入如下代碼:
if(isBuildModule.toBoolean()){apply plugin: 'com.android.application' }else{apply plugin: 'com.android.library' }- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
如果我們需要把租房模塊打包成一個(gè)單獨(dú)的租房app,像下面這樣就好:
我們可以把Basic Component Layer和Business Component Layer放在一起看做是Anjuke SDK,新的業(yè)務(wù)或者項(xiàng)目只需要依賴Anjuke SDK就好(這一點(diǎn)同樣是受到了Trinea文章的啟發(fā))。甚至我們可以做得更極致一些,開發(fā)一套自己的組件管理平臺(tái),業(yè)務(wù)方可以根據(jù)自己的需求選擇自己需要的組件,定制業(yè)務(wù)專屬的Anjuke SDK。業(yè)務(wù)端和Anjuke SDK的關(guān)系如下圖所示:
最后看看安居客模塊化的整體設(shè)計(jì)圖:
模塊化拆分對(duì)于安居客這種比較大型的商業(yè)項(xiàng)目而言,由于歷史比較久遠(yuǎn)很多代碼都運(yùn)行五六年了;各個(gè)業(yè)務(wù)相互交叉耦合嚴(yán)重,所以實(shí)施起來還是有很大難度的。過程中難免會(huì)有預(yù)料不到的坑,這就需要我們對(duì)各個(gè)業(yè)務(wù)有較深的理解同時(shí)也要足夠的耐心和細(xì)致。雖然辛苦,但是一旦完成模塊化拆分對(duì)整個(gè)團(tuán)隊(duì)及公司業(yè)務(wù)上的幫助是很大的。
以上是我的簡單總結(jié)以及對(duì)模塊化的一些思考,不足之處還望大家批評(píng)指正。后面模塊化的demo完善后我會(huì)把它放到GitHub,并再出一篇文章詳細(xì)介紹模塊化的設(shè)計(jì)實(shí)現(xiàn)細(xì)節(jié)。
參考資料:
- http://www.csdn.net/article/2015-12-16/2826499-android-app-architecture?locationNum=7&fps=1
- http://www.trinea.cn/android/didi-internationalization-android-evolution/
- https://www.tianmaying.com/tutorial/AndroidMVC
- https://www.diycode.cc/topics/362
- https://github.com/MDCC2016/Android-Session-Slides/blob/master/02-From.Containerization.To.Modularity.pdf
如果你喜歡我的文章,就關(guān)注下我的知乎專欄或者在GitHub上添個(gè)star吧!
- 知乎專欄:https://zhuanlan.zhihu.com/baron
- GitHub:https://github.com/BaronZ88
總結(jié)
以上是生活随笔為你收集整理的安居客Android项目架构演进的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在CentOS上安装宝塔Linux面板
- 下一篇: Android 讯飞离线语音听写/离线语