Android模塊化實踐

隨着APP的不斷迭代,業務愈來愈複雜,代碼量愈來愈多,單個APP的模式已開始影響開發效率,並且原來的單模塊很難進行業務遷移。因此決定採用模塊化/組件化的思想對APP進行重構。android

組件化與模塊化

什麼是組件化/模塊化

組件化和模塊化是當前軟件開發中經常使用的與平臺無關的的解耦手段,被普遍應用在軟件的架構層面。這二者一般是相輔相成,經過組合的方式來使用。它們只是架構方面的一種思想,在代碼的實現層面上沒有多大區別。我的以爲差異也就如下兩點:git

  • 複用性:組件更注重複用性,如開發中用到的網絡組件,圖片組件等, 在每一個項目中均可使用;
  • 應用範圍:複用性決定了應用範圍,也就是說,組件一般指的是底層模塊,公共組件等。而模塊既可表示上層的業務,也可表示組件中的某個業務功能,如圖片組件中的緩存模塊,下載模塊等。因此模塊的應用範圍更廣。

爲何須要組件化/模塊化

在早期的Java開發中,提倡將整個項目結構按照程序的邏輯結構進行分層,好比表示數據的Dao層,表示控制器的Control層,表示View的View層,可是隨着業務的不斷迭代,發現這種分層方式有很大的弊端,代碼難以定位,且後期難以維護。隨後就出現了以業務結構劃分的模式,這種結構完全解決了以上問題。因此當前的APP基本上使用的都是這種以業務劃分的模式,但隨着App的不斷迭代,業務變得愈來愈複雜,代碼量愈來愈多,維護也變得愈來愈困難。還有一個明顯的問題,Gradle在編譯的時候花費的時間愈來愈長,這大大下降了APP的開發效率。既然單個APP難以解決這個問題,那能夠將項目進行拆分,每一個人只須要負責開發、維護本身的模塊便可。如何拆分呢?使用模塊化技術按業務邏輯將APP進行劃分,使得這些被拆分出來的模塊可單獨運行,這樣就提升了編譯速度,下降了維護成本。github

組件化/模塊化的優勢:
  • 解耦,重用;
  • 下降維護成本,提升開發效率;

模塊化的項目結構

此次重構採用層次化的方式,模塊化的思想,對APP進行了完全的重構,具體的項目結構以下圖所示:數據庫

image

從結構上來看,APP被劃分紅5層,每層的功能具體以下:api

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, 爲頁面的代碼提供統一的結構,頁面的樣式提供統一的風格。app

業務通用UI

主要包含各類樣式的Dialog, 自定義View等,根據APP的設計風格提供統一的樣式;

圖片操做庫

圖片操做庫ImageSet是對圖片組件庫(包括Fresco, Glide, Universer ImageLoader)的封裝,同時提供了調用系統相機/相冊選擇&裁剪照片,相似微信選擇圖片的組件,圖片上傳,圖片壓縮等功能。這個小模塊其實也可放在Common組件層,只是以爲這裏面也有一些業務相關的功能,因此就放在了這一層。

固然,基礎業務層還還包括APP設計風格中須要用到的各類動畫,樣式,顏色值,尺寸值等資源。在進行業務開發時,統一使用這些資源,爲後續修改整個APP風格提供可能。

Common組件層

這裏包含了一些通用的組件,包括各類經常使用的工具類,通用的UI庫,數據源的封裝(包括網絡,文件,數據庫)。這是一個APP的基本架構,裏面包含的類基本不須要改動。因此在對工具類和通用UI進行定義時,須要考慮放置的位置是否準確。

以上是整個項目使用組件化/模塊化後基本結構的詳細介紹,可是在開發過程當中仍是遇到了不少問題。

遇到的問題

模式切換

從Android工程的結構能夠看出,app模塊和新建的其餘Module的結構基本一致,最大的區別就是build.gradle的結構:

// app模塊中build.gralde的結構:
apply plugin: 'com.android.application'

// 其餘Module中build.gralde的結構:
apply plugin: 'com.android.library'
複製代碼

因此Module是否可以運行,關鍵就在於plugin的類型。將新建Module的build.gradle中的'com.android.library' 改爲 'com.android.application',同步以後選擇相應的模塊運行便可。

因此模式的切換隻須要根據條件進行判斷便可,咱們可在gradle.properties中定義一個常量,控制Module的運行模式:

gradle.properties中定義IS_MODULE:

IS_MODULE = false
複製代碼

而後在Module的build.gradle中添加條件判斷:

if (IS_MODULE.toBoolean()) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
複製代碼

這樣在進行模式切換時,只須要修改IS_MODULE的值便可。

AndroidManifest的合併問題

APP在進行打包時,會將全部依賴的Module中的AndroidManifest文件進行合併,具體的合併規則參考合併多個清單文件。合併最基本的原則:只能有一個Application配置了name屬性,只能有一個Activity被配置成了主Activity。可是Module中若是不配置Applicaition中name屬性,就不能進行相應的初始化,若是不指定主Activity,APP也沒法運行。這裏可以使用兩種方案來解決:

  • Module依然當作library使用,Module中的AndroidManifest也不須要指定Application的name屬性和主Activity,直接加載Main模塊(SplashActivity做爲主Activity),在SplashActivity中動態修改要加載的模塊。
  • 在Module中其餘路徑下新建一個AndroidManifest文件(其中爲application標籤指定了name屬性,同時指定了主Activity),而後在build.gradle中根據IS_MODULE的值動態指定AndroidManifest的路徑,這樣Module在不一樣模式下使用不一樣的AndroidManifest文件就避免了合併出錯的問題。但這種方案每一個Module須要提供單獨的Application類。

module/build.gradle

sourceSets {
    main {
        if (IS_MODULE.toBoolean()) {
            manifest.srcFile '../模塊名/src/main/module/AndroidManifest.xml'
        } else {
            manifest.srcFile '../模塊名/src/main/AndroidManifest.xml'
        }
    }
}
複製代碼

若是選擇了雙AndroidManifest文件的方式,那麼在做爲Library的AndroidManifest(默認的)中組件的定義應儘可能簡單,包括主題都不要定義,這樣合併時就不多出現合併錯誤。

若是在AndroidManifest.xml中定義了元數據,如極光推送:

<meta-data
    android:name="JPUSH_CHANNEL"
    android:value="${JPUSH_CHANNEL}" />
<meta-data
    android:name="JPUSH_APPKEY"
    android:value="${JPUSH_APPKEY}" />
複製代碼

那麼除了在當前Module的build.gradle中定義JPUSH_CHANNEL和JPUSH_APPKEY:

manifestPlaceholders =[
    JPUSH_PKGNAME : "com.sxu.library",
    JPUSH_APPKEY : "appkey",
    JPUSH_CHANNEL : "channel"
]
複製代碼

還須要在全部包含此Module的build.gradle中定義這兩個常量,不然會出現manifest合併錯誤。

build.gradle的定義問題

引用Module問題

在引用可切換運行模式的Module時,要根據isModule的值動態添加依賴,若是isModule爲true時,引用的模塊是以Application的形式存在,此時引入時就會報錯。

設置applicationId

applicationId屬性在Application模式下才有效,因此在Module中設置時也須要添加isModule的條件判斷。

依賴管理

爲了便於對依賴和各類SDK版本的管理,最好將這些版本號定義在gradle.properties中統一管理,這樣便於後續的修改。

模塊之間的通訊

組件之間的通訊可以使用EventBus來實現, 可在每一個模塊中新建一個Event類,將同模塊中通訊須要的類都定義在這個Event類中。至於模塊間通訊須要的類,可定義在公共組件層的Event類中(雖然不是很合理,但暫未想到更好的方案)。

模塊對ApplicationContext的引用

在Module的開發中,咱們可能須要引用ApplicationContext對象,但咱們沒有Context對象,沒法直接獲取到,此時可經過如下三種方式解決:

  • 爲須要ApplicationContext對象的類提供init靜態方法,在Application的onCreate中調用:
  • 在Common組件層中提供一個繼承自Application的BaseApplication, 其中包含一個靜態的Context對象,在APP中重寫Application時繼承BaseApplication並對這個靜態的Context對象進行賦值;
  • 在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配置資源名的前綴,一方面避免資源衝突,另外一方面,也便於標識資源所在的模塊:

android {
    resourcePrefix "moduleName_"
}
複製代碼

其餘問題

語法問題

Module中生成的資源Id不是final類型的,因此在onClick中不能使用switch語句塊,只能使用if...else if結構代替。

重構與版本迭代的問題

重構與版本迭代之間衝突是不可避免的問題。 一般重構的時候還須要版本迭代,此時可根據狀況進行人員分配:

  • 有足夠重構的時間:讓大多數人進行重構,只留一兩個進行版本迭代。重構完成後將新版本的內容進行合併;
  • 沒有足夠重構時間:這種狀況就不要作總體重構,而是根據模塊逐漸進行。

重構是一項體力活,也是一項出力不討好的活,畢竟重構以後不可避免地會出現不少Bug, 若是用戶量龐大,那後果可能很嚴重,因此在重構時最好閱讀一下原來的代碼,認真梳理一下業務邏輯再進行。

單Activity模式

在重構的過程當中對其中一個模塊嘗試使用了單Activity模式(頁面統一使用Fragment實現)。體驗感受不錯,值得一試。

總結

模塊化/組件化是一種與技術無關的架構思想,合理的應用可大大下降項目的耦合度。爲了可以快速開發一款新的應用,現已開源了一個通用的APP框架SimpleProject,採用分層+模塊化的思想,詳見Android模塊化框架介紹

相關文章
相關標籤/搜索