Android模塊化實踐
組件化與模塊化
什麼是組件化/模塊化
組件化和模塊化是當前軟體開發中常用的與平台無關的的解耦手段,被廣泛應用在軟體的架構層面。這兩者通常是相輔相成,通過組合的方式來使用。它們只是架構方面的一種思想,在代碼的實現層面上沒有多大區別。個人覺得差別也就以下兩點:
1、復用性:組件更注重複用性,如開發中用到的網路組件,圖片組件等, 在每個項目中都可使用;
2、應用範圍:復用性決定了應用範圍,也就是說,組件通常指的是底層模塊,公共組件等。而模塊既可表示上層的業務,也可表示組件中的某個業務功能,如圖片組件中的緩存模塊,下載模塊等。所以模塊的應用範圍更廣。
為什麼需要組件化/模塊化
在早期的Java開發中,提倡將整個項目結構按照程序的邏輯結構進行分層,比如表示數據的Dao層,表示控制器的Control層,表示View的View層,但是隨著業務的不斷迭代,發現這種分層方式有很大的弊端,代碼難以定位,且後期難以維護。隨後就出現了以業務結構劃分的模式,這種結構徹底解決了以上問題。所以當前的APP基本上使用的都是這種以業務劃分的模式,但隨著App的不斷迭代,業務變得越來越複雜,代碼量越來越多,維護也變得越來越困難。還有一個明顯的問題,Gradle在編譯的時候花費的時間越來越長,這大大降低了APP的開發效率。既然單個APP難以解決這個問題,那可以將項目進行拆分,每個人只需要負責開發、維護自己的模塊即可。如何拆分呢?使用模塊化技術按業務邏輯將APP進行劃分,使得這些被拆分出來的模塊可單獨運行,這樣就提高了編譯速度,降低了維護成本。
組件化/模塊化的優點:
1、解耦,重用;
2、降低維護成本,提高開發效率;
模塊化的項目結構
這次重構採用層次化的方式,模塊化的思想,對APP進行了徹底的重構,具體的項目結構如下圖所示:
從結構上來看,APP被劃分成5層,每層的功能具體如下:
APP殼工程
這是一個空的項目,其中只包含了一個Application的子類和一個IntentService的子類,主要用來對APP中使用的各種組件進行初始化,IntentService的作用是為了提高APP冷啟動的速度,將各種組件的初始化放在後台線程非同步執行,這裡需要注意的是,對於在Applicaiton或SplashActivity中就會使用的組件,最好直接在Application中進行初始化,否則會拋出未初始化的異常。
業務層
這裡的業務層被劃分為Main模塊和其他模塊(至於劃分幾個模塊,根據自己APP的業務,選擇合適的粒度進行劃分即可)。這裡的Main模塊主要包括:新用戶引導頁,啟動頁,主頁。具體業務方面的頁面,都放在具體的業務模塊中。
公共組件層
公共組件層主要包括APP中使用的第三方組件,這些組件基本都是現在APP的通用功能,為上層的業務層提供支持。至於模塊劃分,雖然這些都是單獨的組件,但是每個做為一個Module未免有些繁瑣了,所以還是推薦放在一個Module中。在選擇第三方的庫時,需要做一定的調研,盡量選擇大公司,使用用戶多的SDK,同時在使用時最好封裝一下,這樣後面更換時也方便。
基礎業務層
基礎業務層主要用來統一APP的代碼結構,UI風格等,主要包含以下三個方面:
Android組件的二次封裝
主要是對Activity/Fragment的封裝,提供了不需網路請求的BaseActivity/BaseFragment和需要網路請求的BaseProgressActivity/BaseProgressFragment, 為頁面的代碼提供統一的結構,頁面的樣式提供統一的風格。
業務通用UI
主要包含各種樣式的Dialog, 自定義View等,根據APP的設計風格提供統一的樣式;
圖片操作庫
圖片操作庫ImageSet是對圖片組件庫(包括Fresco, Glide, Universer ImageLoader)的封裝,同時提供了調用系統相機/相冊選擇&裁剪照片,類似微信選擇圖片的組件,圖片上傳,圖片壓縮等功能。這個小模塊其實也可放在Common組件層,只是覺得這裡面也有一些業務相關的功能,所以就放在了這一層。
當然,基礎業務層還還包括APP設計風格中需要用到的各種動畫,樣式,顏色值,尺寸值等資源。在進行業務開發時,統一使用這些資源,為後續修改整個APP風格提供可能。
Common組件層
這裡包含了一些通用的組件,包括各種常用的工具類,通用的UI庫,數據源的封裝(包括網路,文件,資料庫)。這是一個APP的基本架構,裡面包含的類基本不需要改動。所以在對工具類和通用UI進行定義時,需要考慮放置的位置是否準確。
以上是整個項目使用組件化/模塊化後基本結構的詳細介紹,但是在開發過程中還是遇到了很多問題。
遇到的問題
模式切換
從Android工程的結構可以看出,app模塊和新建的其他Module的結構基本一致,最大的區別就是build.gradle的結構:
所以Module是否能夠運行,關鍵就在於plugin的類型。將新建Module的build.gradle中的"com.android.library" 改成 "com.android.application",同步之後選擇相應的模塊運行即可。
所以模式的切換隻需要根據條件進行判斷即可,我們可在gradle.properties中定義一個常量,控制Module的運行模式:
gradle.properties中定義IS_MODULE:
然後在Module的build.gradle中添加條件判斷:
這樣在進行模式切換時,只需要修改IS_MODULE的值即可。
AndroidManifest的合併問題
APP在進行打包時,會將所有依賴的Module中的AndroidManifest文件進行合併,具體的合併規則參考合併多個清單文件。合併最基本的原則:只能有一個Application配置了name屬性,只能有一個Activity被配置成了主Activity。但是Module中如果不配置Applicaition中name屬性,就不能進行相應的初始化,如果不指定主Activity,APP也無法運行。這裡可使用兩種方案來解決:
1、Module依然當做library使用,Module中的AndroidManifest也不需要指定Application的name屬性和主Activity,直接載入Main模塊(SplashActivity作為主Activity),在SplashActivity中動態修改要載入的模塊。
2、在Module中其他路徑下新建一個AndroidManifest文件(其中為application標籤指定了name屬性,同時指定了主Activity),然後在build.gradle中根據IS_MODULE的值動態指定AndroidManifest的路徑,這樣Module在不同模式下使用不同的AndroidManifest文件就避免了合併出錯的問題。但這種方案每個Module需要提供單獨的Application類。
module/build.gradle
模塊之間的通信
組件之間的通信可使用EventBus來實現, 可在每個模塊中新建一個Event類,將同模塊中通信需要的類都定義在這個Event類中。至於模塊間通信需要的類,可定義在公共組件層的Event類中(雖然不是很合理,但暫未想到更好的方案)。
模塊對ApplicationContext的引用
在Module的開發中,我們可能需要引用ApplicationContext對象,但我們沒有Context對象,無法直接獲取到,此時可通過以下三種方式解決:
1、為需要ApplicationContext對象的類提供init靜態方法,在Application的onCreate中調用:
2、在Common組件層中提供一個繼承自Application的BaseApplication, 其中包含一個靜態的Context對象,在APP中重寫Application時繼承BaseApplication並對這個靜態的Context對象進行賦值;
3、在Common組件中提供一個ContentProvider組件,使用靜態的Context對象保存ApplicationContext對象(ContentProvider在系統創建Application對象後就會載入,具體細節查看APP的啟動過程的源碼);
模塊的依賴
模塊之間的依賴
除了Common組件層外,其他的層依賴原則——同層不依賴,下層不依賴上層。
同層之間的依賴主要表現在業務層,這是不可避免的,但我們需要避免互相引用的問題,在業務層,我們可使用隱式跳轉的方式或使用阿里開源的路由框架ARouter實現。
模塊之外的依賴
Gradle3.4中提供了新的依賴配置的關鍵字:
implementation:依賴項在編譯時對所在模塊可用,在運行時對依賴該模塊的模塊可用;
api: 依賴項在編譯時對所在模塊可用,在編譯時和運行時對依賴該模塊的模塊可用;
Gradle3.4中提供的兩個關鍵字相當於對是之前的compile關鍵字進行細化。使用Gradle3.4之前的版本,引入依賴項時都使用compile關鍵字,compile關鍵字容易引起多次引入的問題。使用Gradle3.4之後的版本引入library時,對於外部不需要直接引用的library,最好使用implementation關鍵字,而對於外部需要引用的library, 可使用api(此時就相當於compile)。
資源的命名問題
為了避免資源衝突的問題,我們可在Module中的build.gradle配置資源名的前綴,一方面避免資源衝突,另一方面,也便於標識資源所在的模塊:
其他問題
語法問題
Module中生成的資源Id不是final類型的,所以在onClick中不能使用switch語句塊,只能使用if...else if結構代替。
重構與版本迭代的問題
重構與版本迭代之間衝突是不可避免的問題。 通常重構的時候還需要版本迭代,此時可根據情況進行人員分配:
1、有足夠重構的時間:讓大多數人進行重構,只留一兩個進行版本迭代。重構完成後將新版本的內容進行合併;
2、沒有足夠重構時間:這種情況就不要做整體重構,而是根據模塊逐漸進行。
重構是一項體力活,也是一項出力不討好的活,畢竟重構之後不可避免地會出現很多Bug, 如果用戶量龐大,那後果可能很嚴重,所以在重構時最好閱讀一下原來的代碼,認真梳理一下業務邏輯再進行。
單Activity模式
在重構的過程中對其中一個模塊嘗試使用了單Activity模式(頁面統一使用Fragment實現)。體驗感覺不錯,值得一試。
模塊化/組件化是一種與技術無關的架構思想,合理的應用可大大降低項目的耦合度。為了能夠快速開發一款新的應用,現已開源了一個通用的APP框架SimpleProject,目前只完成了Common組件層,隨後會逐漸進行完善。![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
TAG:杭州田丁科技 |