做者:郭孝星html
校對:郭孝星android
文章狀態:已完成git
關於項目github
BeesAndroid項目旨在經過提供一系列的工具與方法,下降閱讀Android系統源碼的門檻,讓更多的Android工程師理解Android系統,掌握Android系統。面試
文章目錄微信
- 一 發現問題
- 二 提出方案
- 2.1 模塊容器
- 2.2 模塊架構
- 2.3 模塊通訊
- 2.4 模塊生命週期
- 2.5 模塊初始化
- 三 解決問題
模塊化也是近兩年常常被說起的一個技術點,究其緣由,隨着公司業務的逐漸壯大,主應用的工程體積也逐漸變大,管理和編譯都變得十分困難。再加上隨着公司業務的發展,主應用功能拆分和研發團隊的拆分已成必然,這就要求 主應用裏的各個模塊可以獨立編譯、獨立運行、不與主工程以及其餘模塊相互耦合。網絡
模塊化的過程實際上是一個解決技術債的過程,每一個公司的技術債也各不相同,由於模塊化的過程是一個因地制宜的過程,沒有放之四海而皆準的方案,通常說來,模塊化分爲如下三步:數據結構
- 發現問題:發現問題就是理清公司現用的技術架構,清理技術債。
- 提出方案:提出方案包含兩個方面,一方面試新的工程架構,另外一方面是作好新需求排期的安排(是否會阻塞新需求)。
- 解決問題:新方案的推行也是逐步進行的,新的模塊要作好灰度發佈,應用回滾等工做。
而模塊化實踐起來並非一件簡單的事情,每家的應用都有本身的特殊狀況,沒有放之四海而皆準的技術方案,總體上來講,模塊的拆分牽扯工程框架(MVP)、模塊通訊(進程內、跨進程)、Library多端複用、資源拆分等多種 狀況,那麼模塊化最終要達到一個什麼樣的目標呢?🤔架構
- 主應用的其餘模塊能夠快速移植到其餘應用。
- 減小Build時間,各模塊交由各團隊獨立負責,代碼責任制。
- 主應用的各模塊能夠拆分紅獨立的應用,模塊功能服務化。
- 模塊能夠獨立開發、獨立編譯、獨立運行,無需藉助任何主工程環境,模塊之間能夠快速替換。
- 無侵入式的配置各類獨立服務,例如:帳戶信息、設置信息、網絡服務、圖片加載服務、埋點服務、下拉刷新樣式、錯誤狀態等。
- Library能夠快速便捷的在多端使用。庫裏功能儘可能獨立在View或者Fragment,在使用的時候能夠直接添加到宿主Activity裏,宿主Activity能夠本身添加下載刷新樣式、Action bar樣式等。
理解了具體的模塊化需求,咱們接下來開始真正的開始進行模塊化,光說不練假把式,空談沒有任何意義。下面的模塊化都是圍繞着我司主應用大風車而展開的。微信公衆平臺
廣告時間到😎
大風車:http://dafengche.souche.com/
一款SaaS產品,提供建站系統、ERP、CRM、微信營銷系統、財務系統等解決方案,旨在幫助車商及4S集團提高運營和管理水平。
在分析方案以前,首先咱們要知道咱們的應用出了什麼問題,針對大風車這個項目,咱們來具體分析下。
一 發現問題
大風車與2015年上線,通過三年的發展,業務有了很大的增加,功能也逐漸完善,大風車裏程碑以下所示:
咱們和其餘團隊同樣,在業務的發展中,主工程的架構也在不斷的變化,我簡單總結一下:
- 微型項目:早期就是一個工程,幾我的,那個時候也是業務跑量的時候,沒有特別注意架構桑的問題。
- 小型項目:隨着業務的發展,業務種類也逐漸增多,這個時候咱們就把一些業務模塊拆分紅了獨立的Library,體抽了一個Base Library,提供了一些工具庫和樣式上的東西。
- 中型項目:業務進一步增加,單純搞Module Library已經很差用了,這個時間插件化框架很火,很強大,可是問題也不少,咱們最終採用了Router的方式實現了一套僞模塊化方案。
- 大型項目:時間來到了如今,公司業務有了爆發式的增加,公司的應用也有原來的2個變成了5個,並且還有不少定製App、影子App,模塊App等需求提交給咱們,在上一套僞模塊化方案的基礎 上,咱們要實現一套真正的模塊化方案。
大風車工程架構以下圖所示:
能夠看到整個大風車的主工程能夠分爲四層:
- 主工程業務層
- 模塊業務層
- 公司框架層
- 第三方框架層
因此你能夠看到這個工程與模塊之間、模塊與模塊之間的依賴關係真的是美如畫😅,相互引用致使擴展性和可維護性都不好,並且難以測試。咱們來看看這種項目架構的問題在哪裏:
- 模塊邊界被破壞,模塊之間相互依賴,模塊升級複雜,測試困難。
- 基礎工程中心化,類庫積累太重,難以維護。
- 模塊依賴主工程,全部模塊沒法獨立編譯、獨立發佈,編譯耗時,APK體積巨大,多團隊沒法並行開發。
二 提出方案
咱們先來看一看重構後的架構,以下所示:
重構後的大風車採用多容器架構,咱們來看看這套架構是如何實現的。
2.1 模塊容器
既然要把業務模塊化,那就要有承載模塊的容器,目前來講主要用如下三種容器:
- Native容器:Android/iOS原生的容器,承載使用原生實現的業務,例如Android就有Activity容器、Fragment容器以及更加細粒度的View容器。
- H5容器:傳統WebView承載的頁面。
- ReactNative/Weex/Flutter容器:這是自Facebook從15年推出RN方案開始後,流行起來的方案,這套方案的思想就是將JS組件轉義成Native組件,從而實現一套界面,多端運行的效果。
👉 注:手淘提供了細粒度的View容器方案:Virtualview-Android,它能夠經過下發XML配置文件,動態的渲染View。
從長遠來看,這三套容器都不是用來相互取代對方,而是會長期並存,取長補短,相互助益。
- Native容器:Native容器適合用來編寫應用的基礎骨架頁面,例如主頁等,這在iOS上也用來避免審覈上的問題。
- H5容器:H5容器適合用來編寫常常須要變化的頁面,例商家活動頁等。
- ReactNative/Weex/Flutter容器:這一類容器就適合用來編寫常規的頁面界面,因爲這一類容器也自然帶有熱更新能力,因此它也能夠用來解決動態發佈,熱修復等方面的問題。
那如何實現這三套容器呢?🤔
- Native容器:插件化方案,插件化方案大致都比較類似,具體能夠參見我這一篇文章的討論VirtualAPK。
- H5容器:WebView封裝,Jockey通訊協議封裝。
- ReactNative/Weex/Flutter容器:ReactNative/Weex/Flutter容器工程化體系搭建,事實上,用RN或者Weex寫頁面是十分簡單的,它的複雜性在於工程化體系的搭建。
這三套容器的實現,咱們後續都有詳細的文章來討論,咱們接着來看看模塊架構的實現。
2.2 模塊架構
一個良好的系統設計縱向分層,橫向模塊化。咱們來看看從縱向和橫向的角度如何去設計一個模塊。
2.2.1 縱向架構
通常說來,從縱向角度,一個模塊通常能夠劃分爲三個部分:
- Api層:接口部分,提供對外的接口和數據結構。
- Implementation層:實現部分,提供對業務邏輯的實現,它每每和應用的狀態、帳戶信息等息息相關,library爲它提供具體的功能,它決定如何去加載、組織、以及展現這些功能。
- Library層:功能部分,爲implementation提供一些具體的功能。
一個模塊就這樣能夠被劃分爲三層,若是是更加複雜的模塊,咱們還有作好層與層間的解耦與通訊,咱們接着來看一下橫向架構如何實現。
2.2.2 橫向架構
橫向架構就是如何去處理視圖、數據與業務邏輯的關係,關於這一塊內容的實踐,從最初的MVC、到MVP、MVVM,各類架構的目的都都是但願模塊的耦合性更低、獨立性更強,移植性更好。
Google本身也開了一個Repo來討論這些框架的最佳實踐,以下所示:
- MVC:PC時代就有的架構方案,在Android上也是最先的方案,Activity/Fragment這些上帝角色既承擔了V的角色,也承擔了C的角色,小項目開發起來十分順手,大項目就會遇到 耦合太重,Activity/Fragment類過大等問題。
- MVP:爲了解決MVC耦合太重的問題,MVP的核心思想就是提供一個Presenter將視圖邏輯I和業務邏輯相分離,達到解耦的目的。
- MVVM:使用ViewModel代替Presenter,實現數據與View的雙向綁定,這套框架最先使用的data-binding將數據綁定到xml裏,這麼作在大規模應用的時候是不行的,不過數據綁定是 一個頗有用的概念,後續Google又推出了ViewModel組件與LiveData組件。ViewModel組件規範了ViewModel所處的地位、生命週期、生產方式以及一個Activity下多個Fragment共享View Model數據的問題。LiveData組件則提供了在Java層面View訂閱ViewModel數據源的實現方案。
Google官方也提供了MVP的實現,這個MVP框架的核心思想以下所示:
- 使用Contract接口統一管理View接口和Presenter接口的定義,固然這個也不是必定非得這麼寫,並非每一個View接口和Presenter接口均可以成對出現,可能會出現一個VIew接口對應介個Presenter接口或者 一個Presenter接口對應幾個View接口的狀況。
- 採用Fragment實現View接口,咱們知道Presenter接口主要定義的是業務邏輯,例如:加載下一頁、下拉刷新、編輯、提交、刪除等,這些都是在頁面的生命週期方法或者setXXXListener裏調用的,Fragment的生命 週期正好能夠用的上,並且Fragment還能夠獨立的填充到其餘Activity裏。
官方的這套框架存在兩個問題:
- 正如上面所說的View接口交由Fragment實現,可是若是一個頁面由多個獨立的子頁面組合而成,那是否是要在這個頁面添加幾個Fragment,這顯示是不合理的,鑑於這種狀況,咱們能夠 退而求其次,採用自定義View的方式來實現View接口。
- 當頁面增大到必定的量級的時候,就出出現大量的Presenter實現類,其實大風車現有的工程就有不少的Presenter實現類,Presenter實現類和View實現類須要相互set,以便View能夠調用Presenter加載數據 ,Presenter調用View刷新UI,管理這些Presenter類是個很大的問題,並且若是別人要繼承你這個View,你還要告訴它在View的生命週期裏如何去處理Presenter的建立和銷燬,以及什麼時候去加載數據等等。 若是出現跨部門甚至跨跨城市的合做時,溝通成本就很是的高。
總的說來,就是當業務量急劇膨脹的時候,就會須要寫大量的View接口和Presenter類,並且這還牽扯到Presenter類與Activity生命週期同步的問題,在大型項目面前,這些操做都會變得十分複雜。
綜上所述,一個理想的方案就是結合ViewModel組件與LiveData組件來實現MVVM框架。
這套框架有兩個重要的原則:
- 任何不處理UI邏輯和用戶交互的代碼都不該該寫到Activity或者Fragment中,由於Activity或者Fragment是十分脆弱的,低內存、配置發生變化、進入後臺等等均可能致使它們的銷燬,應該 最大限度的減低對Activity或者Fragment的依賴。
- 應該使用一個持久數據模型來驅動咱們的UI,數據能夠在該套模型裏進行持久化,一旦Activity或者Fragment被銷燬,用戶數據不會丟失,這套模型專門用來處理數據邏輯,使應用的數據邏輯與視圖邏輯 向分離,讓應用變得更易維護。
👉 注:這裏可能有人有疑問,非得用Lifecycle組件嗎,利用View的onAttachToWindow()、onDetachToWindow()這些方法來模擬Activity或者Fragment的生命週期不能夠嗎,事實上View的生命週期在 一些特殊的場景下是不可靠的,例如:RecyclerView、ViewPager,因此咱們仍是須要利用Lifecycle組件來監聽Activity或者Fragment的生命週期變化。
2.3 模塊通訊
解決了模塊間的解耦問題,另外一個就是模塊間的通訊問題。在一個大型的應用裏不少模塊都是能夠獨立運行甚至獨立成一個App的,這就牽扯到模塊間的數據交互和通訊問題,例如:最多見的一種 場景就是子模塊須要知道主應用裏的登陸信息等等,模塊間的通訊業能夠分爲兩種狀況:
- 進程內通訊:模塊都運行在同一個進程中。
- 跨進程通訊:模塊運行在不一樣的進程中。
2.3.1 進程內通訊
進程內通訊的手段有不少種,最多見的就是EventBus,
EventBus 用來完成 Activities, Fragments, Threads, Services 之間的數據交互和通訊。
EventBus是早期頁面通訊和模塊通訊常見的手段,它的好處是顯而易見的,將事件的發佈者與訂閱者解耦,無需再定義一堆複雜的回調接口,可是隨着工程的 膨脹,它的問題也凸顯出來,具體說來:
- Event並不是全部通訊常見的最佳方式,它主要適合一對多的廣播場景,若是業務中的通訊須要一組接口時,就須要定義多個Event,代碼複雜。
- 大量的Event的類,難以管理,若是應用愈來愈龐大,模塊劃分也愈來愈多,這個Event就變得難以維護。
可是即使這樣,EventBus仍是一個優秀的進程內通訊的方式。
👉 注:固然除了EventBus之外,在簡單的通訊場景下,咱們還能夠選擇LocalBroadcastReceiver。LocalBroadcastReceiver是一個應用內的局域廣播,它也是利用一個Looper Handler維護一個 全局Map進行應用內部通訊,與EventBus不一樣,它發送的是字符串。LocalBroadcastReceiver在面臨業務膨脹的時候,也會遇到消息字符串的管理問題。
2.3.2 進程間通訊
跨進程通訊能夠藉助Content Provider來完成,
Content Provider 底層採用的是Binder機制,用來完成進程間的數據交互和通訊。
模塊通訊採用Content Provider的方式來解決,一個比較常見的場景就是多模塊共享登陸信息,登陸信息能夠用Content Provider來保存,當登陸狀態發生變化時,能夠通知到 各個模塊。
經過上面的分析,咱們已經完成了一個設計良好的模塊,可是模塊的接入仍然面臨着諸多問題,例如:如何界定模塊的生命週期,用戶信息等如何同步,模塊如何進行註冊以及初始化 等問題。少許的模塊,這些都不是問題,可是當模塊增加到必定的數量級的時候,這個問題就會變得十分突出。
2.4 模塊生命週期
模塊生命週期的生命週期能夠作以下劃分:
- 進程啓動:執行模塊的初始化。
- Account初始化:執行模塊用戶信息同步,告知模塊用戶已經登陸。
- Account註銷:執行模塊用戶信息同步,告知模塊用戶已經註銷。
- 進程退出:執行模塊的退出。
2.5 模塊初始化
模塊的初始化通常在Application裏進行,固然也有懶加載的模塊,模塊的初始化通常傳遞應用上下文信息,用戶信息,配置參數等信息,這裏能夠考慮對模塊進行自動初始化,具體 流程以下所示:
- 添加依賴,依賴也分爲兩種:編譯期依賴和運行期依賴,
- 配置數據,註冊服務。
- 啓動服務。
三 解決問題
模塊化拆分不是一個簡單的事情,無法一蹴而就,也不可能讓團隊所有停下來去作拆分重構,因此真正實施模塊化須要按照如下幾個步驟按部就班的進行。
- 心態調整
技術上的重構並不能帶來短時間上的收益,它是一個長時間才能顯現好處的事情,你每每花費了不少時間來作這些事情,它也很是的有意義,可是老闆看不到,業務上也不會帶來明顯的增加。因此 第一件事情,就是作好團隊成員的思想工做。
事實上,大部分研發同窗都仍是很是有技術追求的,可是咱們工程一般有不少歷史遺留問題,也就是所謂的技術債,要去重構這些東西,成本是很是高的,面對這種狀況在加上平時業務需求多,時間緊,你們 一般都會想:
重構難度這麼大,出了問題怎麼辦,算了,別人怎麼寫,我也怎麼寫好了。
這是一個很廣泛的現象,這種狀況下就須要有一個有魄力的leader打響第一槍,有了第一個階段的重構,你們看到了曙光,就會開始陸續吐槽原來的設計有多麼爛,應該如何設計等等。
- 模塊拆分:對須要重構的模塊進行拆分,包括代碼,資源等等。
- 灰度發佈:對小部分用戶推送重構版本。
- 應用回滾:對git代碼作好tag,遇到問題時隨時準備迴歸。
附錄
最後囉嗦幾句:
- 能用原生實現的不要用第三方庫實現,若是實在須要第三方庫實現,例如:圖片庫、網絡庫,也不要直接使用,要作好封裝和接口隔離,方便之後作替換。
- 頁面間的繼承關係必定要謹慎,除非是專門爲繼承而設計的頁面,不然應該考慮使用組合或其餘侵入性更低的方式來解決問題。
- 項目中爲某個需求提出瞭解決方案時,若是這種需求其餘團隊還可能會遇到,就要評估一下這個方案耦合性怎麼樣,之後可否直接給其餘團隊使用,較少團隊間 的重複勞動。
- 對外提供的功能儘可能作好接口封裝,不要直接暴露內部細節,這樣往後也能夠直接替換內部邏輯,而不至於影響業務方。
本篇文章到這裏就結束了,歡迎關注咱們的BeesAndroid微信公衆平臺,BeesAndroid致力於分享Android系統源碼的設計與實現相關文章,也歡迎開源愛好者參與到BeesAndroid項目中來。
微信公衆平臺