前言
架構是我們組織程式,各個專案元件的一種機制。好的架構兼顧了易用性,靈活性,擴充套件性和複用性。現代Andorid架構已經不限於單體或者單Module了,逐漸在向著多Module和外掛化動態化進行發展。
這裡主要圍繞專案單體應用時的架構,單Module到多Module的演變,以及外掛化的未來來說。
單體應用架構
單體應用架構是指我們的專案在單Module時的架構,此時專案一般會劃分一些層級,比如UI層,網路層,邏輯層。這些層級怎麼進行組織呢?
最開始,大家剛接觸Android開發,統統是MVC。MVC的問題是佈局檔案作為View層,無法承擔任何業務邏輯;UI的邏輯和業務邏輯都寫在Activity層,在專案體量不大的時候這樣完全沒有問題。可是當每個頁面的業務邏輯變成之前的2倍的時候,程式碼量也會增長一倍,這大大降低了專案的擴充套件性和維護性。這個時候可能你會通過抽取重構的方式來緩解這種局面,可是隨著業務增多,不久又會陷入這種局面。
慢慢的我們發現,將Activity層的所有業務邏輯抽出去,只剩下UI相關邏輯。這樣既清晰,又能解決上面的問題,所以MVP出現了。在MVP中,Activity充當UI層,Presenter充當業務邏輯;UI層和邏輯層互動,邏輯層和資料層互動。採用介面回撥,或者EventBus的方式讓UI層和邏輯層進行通訊。
客戶端開發主要是和UI打交道,最好的一定是MVVM。使用databind,或者LiveData可以替代介面回撥和EventBus。關於上面這些內容,我在另一篇文章中有更詳細的說明:juejin.im/post/5c47e1…
MVVM架構下的單體應用專案,應該有這樣幾個層級:
- 資料層,專門負責資料的存取,方式不限(網路資料,File資料或者KV儲存)
- VM層,專門負責執行業務邏輯和資料的處理,與資料層有互動
- UI層,負責顯示每個介面,彈窗等,與VM層有互動
如下圖示:
單Module到多Module演變
這個演變非常像後臺應用的單體架構到微服務架構的演變,適用於公司內部有多個產品需要同時迭代的場景。隨著單體應用向多Module的演變,原先的各個元件都獨立到不同的Module中去了。
大概有兩個階段:
- 單一入口Module + 公共類庫Module
- 多入口Module + 多公共業務Module + 公共類庫Module
第一個階段很好理解,也是一個自然而然的演變。因為專案中很多公共的模組,比如日誌模組,網路模組,資料模組。這些模組與邏輯無關,可以給任何專案使用,所以單獨開一個Module或者傳到maven庫。如果要開發新的專案,那也只是將公共類庫Module移植過去即可。
所以,第一階段的專案Module結構大概是這樣:
- App Module,負責提供專案入口,完成各個業務邏輯功能
- Library Module,負責提供專案公共元件,比如日誌元件,網路元件,儲存元件
- 其他三方庫Module
以抖音App為例:
上面的階段能做到功能模組的重用,但是沒有涉及到業務邏輯的重用。新專案也有登入註冊功能,難道要重新寫一遍麼?
你可能擔心兩個專案的登入註冊邏輯能一樣麼,介面也不可能一樣啊。介面肯定是不一樣的,但是登入註冊邏輯大部分是一樣的。這就說明我們其實可以對一些公共業務劃分Module,對於不一樣的地方,完全可以動態化。但是公共業務不能劃入類庫Module中,因為通用性不夠;還要注意業務Module的抽離本著只抽業務,不抽UI的原則。
當業務Module和類庫Module抽離之後,不同的專案我們可以新增不同的入口Module,入口Module更多是完成入口功能,和UI定製化功能,以及完成一部分差異化的實在不能共用的業務邏輯。
所以,第二階段的專案Module結構大概是這樣:
- App1 Module,負責提供App1的入口,完成App1的所有UI邏輯和部分不能共用的業務邏輯
- App2 Module,負責提供App2的入口,完成App1的所有UI邏輯和部分不能共用的業務邏輯
- Login Module,負責提供登入註冊功能
- Video Module,負責提供視訊相關功能
- Library Module,負責提供公共模組功能
- 其他三方Module
客戶端開發中,UI的變數最大,上面的Module劃分儘量將變化少的東西複用,將變化大的東西剝離出來。
他們之間的呼叫關係應該符合這樣的規則:
- App入口Module可以呼叫業務Module和Library Module;
- 業務Module可以呼叫Library Module,業務Module之間不能相互呼叫,也但不能呼叫App入口Module,目的是保持單向呼叫流程
- Library Module只能被呼叫。
以抖音App為例:
如果你發現業務Module需要呼叫App入口Module,或者其他情況,可能你的Module架構設計有問題。
Module間通訊
其實上面的架構不存在Module之間的通訊,也不需要。
假設這樣一個場景: 在App1中的登入介面,呼叫的Login模組的登入邏輯,登入完成後需要跳轉使用者資訊介面。
上面的場景只需直接呼叫Login模組的邏輯即可,即便發生了不同UI跳轉,也不需要Module通訊,因為所有的UI都寫在App1中了。
如果當初將登入相關的UI也抽到Login模組中,看起來封裝度更高,其實有很多不方便。在上面的場景中,需要從Login模組的UI呼叫其他模組的UI,這樣呼叫流程變得複雜,而且必須要ARouter這樣的通訊工具;問題是Login的UI在各個App中不太可能一樣,如果一樣,那麼則可以抽進去。
外掛化和熱修復
很多大廠的App功能實在是太多,如果一開始就將所有功能都納入App中,體積下不來,問題是有一些功能很多人從來也不用。
所以從使用程度和重要性程度可以劃分等級,對於那些次要的功能和模組進行動態化。一開始App只提供部分重要和必須的功能,並且提供一個class執行容器。當使用者需要哪些功能是就進行下載,下載下來的無非是一些class和資源,通過定製的類載入器來載入,並做好生命週期管理和資源與Context的統一,這就是大部分外掛化框架的原理。
外掛化對於大部分中小公司是不需要的,可以根據需要採納。
熱修復是指將新功能或者Fixed Bug快速更新到使用者的App中,而不用重新下載App。大致原理是先計算出含有新功能的Apk和舊Apk之間的差異,得到一個diff包,一般很小。然後使用者直接下載diff包,App中預先內建了class動態替換的框架,將diff包載入進來就實現了動態熱修復。目前熱修復存在一些相容性問題。
無論是外掛化還是熱修復,不建議自研,大廠有過豐富的經驗和實踐,可直接使用目前成熟的類庫。
App Bundle
一般來說,我們開發的App,裡面包含了各種dpi的資源,相容各種CPU架構的so庫,這大大增加了Apk的體積。可使用者事實上只需要匹配他手機的一種dpi和一種so庫就足夠,其他的對於使用者來說都是冗餘,上面講的外掛化解決了在功能方面的冗餘問題。
能不能將所有的資源,so庫等也按需下載安裝呢?谷歌在AS3.2中提供的App Bundle功能就是幹這個的。之前使用者需要下載所有的資源,下載只會下載和自己裝置匹配的資源,大大提高了產品的交付速度和交付質量,可是目前只支援Google Play。
App Bundle會將我們Apk的所有東西打為壓縮包上傳到Google Play。而Google Play會在使用者下載的時候,根據裝置特性生成優化過的Apk給使用者安裝。
App Bundle也支援動態apk,支援將暫時不需要的功能配置為動態功能,在使用者需要的時候下載,也就是上面的外掛化的功能。
App Bundle應該是外掛化和動態化的未來,從工程建設的角度幫開發者完成了產品的動態交付。也許,在不久的將來國內商店也會支援這種功能。
最後
每種架構都用自己的優缺點,不能盲目採用多體的架構,也不能偷懶就永遠不升級架構;最終採用什麼樣的架構要根據自己專案體量和發展來判斷。
根據我的經驗,對於大部分公司來說,多體架構的初級階段就完全夠用了。對於同一個團隊需要同時迭代多個專案的場景,可能需要發展到多體架構的第二階段。對於海量功能的App,可能需要外掛化。
這些只是我個人的見解和經驗,希望大家踴躍討論,交流一下你們的寶貴經驗,互相提高下!