安居客Android專案架構演進

張磊BARON發表於2017-02-24

本文已授權微信公眾號 AndroidDeveloper 獨家釋出。

入職安居客三年從工程師到 Team Leader,見證了 Android 團隊一路走來的發展歷程。因此有心將這些記錄下來與大家分享,也算是對自己三年來一部分工作的總結。希望對大家有所幫助,更希望能得到大家寶貴的建議。

一、三網合併

三年前入職時安居客在業務上剛完成了三網合併(新房、二手房、好租和商業地產多個平臺多個網站合成現在的 anjuke.com,這在公司的歷史上稱之為三網合併),因此移動端也將原先的新房、二手房、好租和商業地產多個 App 合併成為了現在的安居客 App。所謂的合併也差不多就是將多個專案的程式碼拷貝到了一起組成了新的 Anjuke Project。下面這張圖能更加直觀的呈現當時的狀況:

圖片名稱

這一時期程式碼結構混亂、層次不清,各業務技術方案不統一,冗餘程式碼充斥專案的各個角落;甚至連基本的包結構也是胡亂不堪,專案架構更是無從談起。大家只不過是不停地往上堆砌程式碼新增新功能罷了。於是我進入公司的第一件事就是向 Leader 申請梳理了整個專案的結構。

而後隨著專案的迭代,我們不斷引入了 Retrofit、UniversalImageLoader、OKHttp、ButterKnife 等一系列成熟的開源庫,同時我們也開發了自己的 UI 元件庫 UIComponent、基礎工具庫 CommonUtils、基於第三方地圖封裝的 MapSDK、即時聊天模組 ChatLibrary 等等。這之後安居客專案架構大致演變成了由基礎元件層、業務元件層和業務層組成的三層架構。如下圖:

圖片名稱

其中業務層是一種非標準的 MVC 架構,Activity 和 Fragment 承擔了 View 和 Controller 的職責:

圖片名稱

前面這種分層的架構本身是沒太大問題的,即使到了現在我們的業務專案也已然是基於這種分層的架構來構建的,只不過在不斷的迭代中我們做了些許調整(分層架構後面在介紹元件化和模組化的時候會詳細介紹)。但是隨著業務的不斷迭代,我們慢慢發現業務層這種非標準的MVC架構帶來了種種影響團隊開發效率的問題:

  • Activity 和 Fragment 越來越多的同時承擔了 Controller 和 View 的職責,導致他們變得及其臃腫且難以維護;
  • 由於 Controller 和 View 的揉合,導致單元測試起來很困難;
  • 回撥巢狀太多,面對負責業務時的程式碼邏輯不清晰,難以理解且不利於後期維護;
  • 各層次模組之間職責不清晰等等

鑑於三網合併時期我還未加入安居客,所以對這一塊的理解難免有偏差,如果有安居客的老同事發現文章中的描述有不對的地方還望批評指正。

二、由 RxJava 驅動的 MVP 架構

一種技術架構無法滿足所有的業務專案,更不可能有一種架構方案能夠一勞永逸。正如上一節中提到的隨著業務的不斷迭代,現有架構的缺陷逐漸浮出水面,專案架構必需不斷升級迭代才能更好地服務於業務。

2.1 MVP 的設計與實現

在研究了 Google 推出的基於 MVP 架構的 Demo 後,我們發現 MVP 架構能解決現在所面臨過的很多問題,於是我們學習並引入到了我們的專案中來,並針對性的做了部分調整。下圖呈現的是安居客 MVP 方案:

圖片名稱

以前面提到的三層架構的方案來看是這樣的:

圖片名稱

基於此架構我在 GitHub 上開源了一個專案MinimalistWeather,有興趣的小夥伴可以去 Clone 下來看看,如果覺得對你有幫助就給個 Star 吧。 :)

  • View Layer: 只負責 UI 的繪製呈現,包含 Fragment 和一些自定義的 UI 元件,View 層需要實現 ViewInterface 介面。Activity 在專案中不再負責 View 的職責,僅僅是一個全域性的控制者,負責建立 View 和 Presenter 的例項;
  • Model Layer: 負責檢索、儲存、運算元據,包括來自網路、資料庫、磁碟檔案和SharedPreferences的資料;
  • Presenter Layer: 作為 View Layer 和 Module Layer 的之間的紐帶,它從 Model 層中獲取資料,然後呼叫 View 的介面去控制 View;
  • Contract: 我們參照 Google 的 Demo 加入契約類 Contract 來統一管理 View 和 Presenter 的介面,使得某一功能模組的介面能更加直觀的呈現出來,這樣做是有利於後期維護的。

另外這套MVP架構還為我們帶來了一個額外的好處:我們有了足夠明確的開發規範和標準。細緻到了每一個類應該放到哪個包下,哪個類具體應該負責什麼職責等等。這對於我們的 Code Review、接手他人的功能模組等都提供了極大的便利。前面提到的 MinimalistWeather 就是為了定規範定標準而開發的。

這一時期我們還在專案中引入了 RxJava,很好的解決了前面提到的巢狀回撥的問題,同時能夠幫助我們簡化複雜業務場景下的程式碼邏輯(當然 RxJava 的好處遠遠不止這麼一點,對 RxJava 不瞭解的同學可以去翻翻我之前一系列關於 RxJava 的文章)。我們也將網路庫升級到了 Retrofit2 + OKHttp3,它們和 RxJava 之間能更好的配合。

2.2 MVP 帶來的新問題及解決方案

是不是升級到了 MVP 架構就高枕無憂了呢?很明顯不是這樣!MVP 架構也會帶來以下新的問題:

  • 由於大量的業務邏輯處理轉移到了 Presenter 層,在一些複雜的業務場景中 Presenter 同樣會變得臃腫難懂。細心的同學可能注意到了前面的架構圖中的 Model 層有個 Data Repository 模組,Data Repository 在這裡有兩個作用:一是可以將原本由 Presenter 處理的部分邏輯轉移到這裡來處理,包括資料的校驗、部分單純只與資料相關的邏輯等等,向 Presenter 遮蔽資料處理細節,比如作為 Presenter 就不必關心 Model 層傳遞過來的資料到底是來至網路還是來至資料庫還是來至本地檔案等等;二是我們引入了 RxJava,但是隻有網路層中的 Retrofit 能返回 Observable 物件,其他模組都是返回的還是一些非 Observable 的 Java 物件,為了能在整個 Presenter 層中都體驗 RxJava 帶來的美妙之處,因此可以通過 Data Repository 做一層轉換;
  • 現在的 MVP 架構中最重的部分就是 Model Layer 了,這一點從前面的架構圖中就能體現。因此這就要求我們在 Model 層的設計過程中職責劃分要足夠清晰,分包更明確,耦合度更低。至於分包大家可以參考 MinimalistWeather 的方案:db 包為資料庫模組、http 包為網路模組、preference 包是對 SharedPreferences 的一些封裝、repository 包就是前面提到的 Data Repository 模組;
  • 同時還有一點需要注意,很多人在使用 RxJava 的過程中往往忘記了對生命週期的管理,這很容易造成記憶體洩露。MinimalistWeather 中採用了 CompositeSubscription 來管理,你也可以使用 RxLifecycle 這類開源庫來管理生命週期。

三、元件化與模組化

去年下半年我們 Android 團隊內部成立了技術小組,基礎元件的開發是技術小組很重要的一部分工作,所以元件化是我們正在做的事;模組化更多的是現有的方案受到來自業務上的挑戰以及受到了 Oasis Feng 在 MDCC 上的分享和整個大環境的啟發,現在正處於設計規劃和 Demo 開發的階段。

3.1 元件化

元件化不是個新概念,通俗的講元件化就是基於可重用的目的,將一個大的軟體系統拆分成一個個獨立元件。

元件化的帶來的好處不言而喻:

  • 避免重複造輪子,節省開發維護成本;
  • 降低專案複雜性,提升開發效率;
  • 多個團隊公用同一個元件,在一定層度上確保了技術方案的統一性。

現在的安居客有是三個業務團隊:安居客使用者 App、經紀人 App、集客家 App。為了避免各個業務團隊重複造輪子,團隊中也需要有一定的技術沉澱,因此元件化是必須的。從本篇的第一節大家就能看到元件化的影子,只不過在這之前我們做的並不好。現在我們需要提供更多的、職能單一、效能更優的元件供業務團隊使用。根據業務相關性,我們將這些元件分為:基礎元件和業務元件。後面在介紹模組化的時候會有進一步的描述。

3.2 模組化

自從 Oasis Feng 在去年的 MDCC2016 上分享了模組化的經驗後,模組化在 Android 社群越來越多的被提起。我們自然也不落俗的去做了一些研究和探索。安居客現在面臨很多問題:例如全量編譯時間太長(我這臺13款的 MacBook Pro 上打一次包得花十多分鐘);例如新房、二手房、租房等等模組間耦合嚴重,不利於多團隊並行開發測試;另外在17年初公司重新將租房 App 撿起推廣,單獨讓人來開發維護一個三年前的專案並不划算,所以我們希望能直接從現在的安居客使用者端中拆分出租房模組作為一個單獨的 App 釋出上線。這樣看來模組化似乎是一個不錯的選擇。

所以我們做模組化的目的大致是這樣的:

  • 業務模組間解耦
  • 單個業務模組單獨編譯打包,加快編譯速度
  • 多團隊間並行開發、測試
  • 解決好租App需要單獨維護的問題,降低研發成本

15年 Trinea 還在安居客的時候開發了一套外掛化框架,但受限於當時的團隊規模並且外掛化對整個專案的改造太大,因此在安居客團隊中外掛化並未實施下來。而模組化其實是個很好的過渡方案,將專案按照模組拆分後各業務模組間解耦的問題不存在了,後續如有必要,再進行外掛化改造只不過是水到渠成的事。

來看看安居客使用者 App 的模組化設計圖:

圖片名稱

整個專案分為三層,從下往上分別是:

  • Basic Component Layer: 基礎元件層,顧名思義就是一些基礎元件,包含了各種開源庫以及和業務無關的各種自研工具庫;
  • Business Component Layer: 業務元件層,這一層的所有元件都是業務相關的,例如上圖中的支付元件 AnjukePay、資料模擬元件 DataSimulator 等等;
  • Business Module Layer: 業務 Module 層,在 Android Studio 中每塊業務對應一個單獨的 Module。例如安居客使用者 App 我們就可以拆分成新房 Module、二手房 Module、IM Module 等等,每個單獨的 Business Module 都必須準遵守前面提到的 MVP 架構。

同時針對模組化我們也需要定義一些自己的遊戲規則:

  • 對於 Business Module Layer,各業務模組之間的通訊跳轉採用路由框架 Router 來實現(可能會採用成熟的開源庫,也可能會選擇重複造輪子);
  • 對於 Business Component Layer,單一業務元件只能對應某一項具體的業務,對於有個性化需求的對外部提供介面讓呼叫方定製;
  • 合理控制各元件和各業務模組的拆分粒度,太小的公有模組不足以構成單獨元件或者模組的,我們先放到類似於 CommonBusiness 的元件中,在後期不斷的重構迭代中視情況進行進一步的拆分(這一點的靈感來源於 Trinea 的文章);
  • 上層的公有的業務或者功能模組可以逐步下放到下層,合理把握好度就好;
  • 各 Layer 間嚴禁反向依賴,橫向依賴關係由各業務 Leader 和技術小組商討決定。

對於模組化專案,每個單獨的 Business Module 都可以單獨編譯成 APK。在開發階段需要單獨打包編譯,專案釋出的時候又需要它作為專案的一個 Module 來整體編譯打包。簡單的說就是開發時是 Application,釋出時是 Library。因此需要你在 Business Module 的 Gradle 配置檔案中加入如下程式碼:

if(isBuildModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}複製程式碼

如果我們需要把租房模組打包成一個單獨的租房 App,像下面這樣就好:

圖片名稱

我們可以把 Basic Component Layer 和 Business Component Layer 放在一起看做是 Anjuke SDK,新的業務或者專案只需要依賴 Anjuke SDK 就好(這一點同樣是受到了 Trinea 文章的啟發)。甚至我們可以做得更極致一些,開發一套自己的元件管理平臺,業務方可以根據自己的需求選擇自己需要的元件,定製業務專屬的 Anjuke SDK。業務端和 Anjuke SDK 的關係如下圖所示:

圖片名稱

最後看看安居客模組化的整體設計圖:

圖片名稱

模組化拆分對於安居客這種比較大型的商業專案而言,由於歷史比較久遠很多程式碼都執行五六年了;各個業務相互交叉耦合嚴重,所以實施起來還是有很大難度的。過程中難免會有預料不到的坑,這就需要我們對各個業務有較深的理解同時也要足夠的耐心和細緻。雖然辛苦,但是一旦完成模組化拆分對整個團隊及公司業務上的幫助是很大的。

以上是我的簡單總結以及對模組化的一些思考,不足之處還望大家批評指正。後面模組化的 Demo 完善後我會把它放到 GitHub,並再出一篇文章詳細介紹模組化的設計實現細節。

參考資料:

如果你喜歡我的文章,就關注下我的知乎專欄或者在 GitHub 上添個 Star 吧!

相關文章