從零開始搭建一個完善的MVP開發框架(五),通過組件化開發優化項目的結構

摘要: 在第三篇文章有位朋友留言說:如果接口的數量有一百個,那麼是不是需要寫一百個Presenter?答案是不一定的,因爲這個問題需要根據實際的業務需求來解決。但是這種一個接口對應一個Presenter的方式能夠對項目進行最大限度的解耦,我們能夠很方便的複用這個接口。

採用MVP模式引發的一些思考

筆者在研究MVP模式的時候查閱過相當多的資料,其中有兩句話令我相當的深刻。一是:使用MVP模式雖然代碼量會大大增加,但是爲了降低耦合和邏輯上的簡潔這個犧牲是值得的。二是:有些公司是根據代碼量來統計工資的,所以使用MVP模式對於那些用代碼量來統計工資的開發者來說,這應該是一個優點(然而,我們公司的工資和代碼量時無關)。我當初就是信了你們的邪,當項目達到一定的程度的時候如果採用網上推薦的方式,重複的代碼多到你無法想象好嗎?每個Presenter都要處理大量重複的邏輯,項目中存在無數個功能相同的Model(向服務器發起請求和發送回調數據到Presenter)例如進度條的顯示等等。不過當筆者意識到這個問題後,就通過泛型和封裝優化了代碼,解決了上述的問題,所以就有了前面的四篇文章。但是有一個問題是沒辦法通過優化代碼解決的,那就是Presenter的管理問題。

這裏筆者說一個例子,在開發的時候我接到一個需求是把應用中的一個模塊完全移植到另外一個應用中。這個需求很簡單,但是有一個問題就是:由於項目達到一定的體量後Presenter的數量和View接口文件比較多,所以在移動模塊的時候需要判斷那些文件時必要的,很容易漏掉某個文件或者添加了多餘的文件。而且由於數據傳遞等原因,把模塊移動到新框架中還需要重新對項目進行調試一遍又一遍才能運行起來。做完這一切所花費的時間並不短,並且還需要重新對這個模塊乃至整個項目都測試一遍。

把一個模塊遷移到另外一個應用中其實是很常見的問題。特別是在同一間公司不同應用中相似模塊背後的邏輯其實是類似的,只是View的展現有所區別而已(例如登陸模塊,登陸模塊採取的邏輯與登陸校驗其實是類似的)。所以如果能把模塊以組件的形式分離出來,當需要開發一個新app的時候,再通過一個app模塊把這些模塊像拼積木一樣拼接起來這樣不就會大大增加我們的工作效率了嗎?

關於組件化開發,筆者也查閱過很多大牛寫的文章,這個概念看起來高尚大,實際上通過gradle可以很簡單地實現組件化。採用組件化進行開發還能順便解決我們管理Presenter的問題。因爲當你採用組件化開發的時候,單個組件中的Presenter的數量其實不多的,所以採用組件化開發不用擔心管理Presenter的問題(什麼,你說10來個Presenter你都無法管理了?我選擇死亡。。。)。如果你們某個業務組件的接口有幾十個的話,這個就是你們的業務架構有問題了。

什麼是組件化開發

組件化開發的核心就是把業務模塊封裝成一個高內聚,低耦合的組件。所謂的高內聚低耦合的基本要點就是,這個業務組件完全不依賴於其它的業務組件,這個組件的功能是完善的能單獨拿出來使用的,並且其它的模塊能夠輕鬆喚起這個組件進行工作。

光靠文字可能大家會覺得有點難以理解,下面來看看一個關於電商平臺的組件化簡圖

電商平臺組件化簡圖

這個APP有4個組件,這四個組件分別依賴於BaseLib組件,而且各個組件都是單獨存在沒有任何交集的。BaseLib中包含了我們前面4篇文章所開發的MVP開發框架組件、baseApp組件(這裏放style,drawable資源和自定義Application)、工具類組件等。

可能有讀者會產生疑問,個人中心和購物車是需要登錄後才能使用的功能,但是這兩個組件和登陸註冊模塊之間是沒有依賴的。那麼怎麼樣判斷App實際有沒有登陸呢?在這裏筆者是通過Base組件中的自定義Applcation實現的。當登陸成功後,把獲取到的密鑰交給Applcation處理就可以了。

組件化開發有什麼優點

  1. 業務組件可以很方便地移植到另外一個App。
  2. 項目的分工可以更加合理,因爲不同組件之間是沒有依賴關係的。所以可以幾個組件同時開發。
  3. 可以實現組件的單獨編譯和測試。
  4. 項目之間的耦合度更低,更好管理。

道理大家都懂,但是具體要怎麼做呢?下面我就來教你們如何實現組件化。

組件化的項目結構

項目的結構

項目結構圖

上圖中的Business就是我們的業務層代碼,其中的test就是我們的一個測試組件。

實現組件化

組件化的關鍵是通過gradle腳本實現組件間的的屬性變化(這裏稱作爲屬性是爲了方便大家理解,實際上這個是一個插件,這個插件的作用時幫助我們構建項目,這裏涉及到gradle自動化構建方面的知識,這裏就不深入了)。何謂屬性變化呢?不知道大家在平時開發的時候要沒有認真看過build.gradle文件,這個文件的第一句代碼就確定了這個Module的屬性。
一般我們在Android Studio創建一個project的時候,我們的項目中就會帶有一個Module,這個Module的名字就叫app。現在我們來看看app中build.gradle的第一句代碼是怎樣寫的:

1
apply plugin: 'com.android.application'

這句代碼的作用是規定了我們app這個Module是一個application,意思就是app這個Module的屬性是application。除了application屬性外我們還需要用的一個屬性是library屬性,這個屬性規定了module是一個library,這兩個屬性只能同時存在一個。我們在前面用到的volley框架就是一個library。下面我們來看看它的build.gradle是怎麼寫的:

1
apply plugin: 'com.android.library'

因爲我們組件化的要求是,組件必須能夠組合一起運行也能單獨開發、單獨測試所以我們的組件需要能夠單獨運行才能滿足我們的要求。
在Android中Module的屬性必須是application才能單獨運行起來。所以當我們開發完一個業務組件之後,我們把build.gradle的屬性改爲application就能單獨運行起來了,如果需要組合在一起則需要把屬性改爲library即可。這裏有一點是需要注意的,當Module的屬性爲application的時候,必須要有一個入口activity,入口activity就是指我們設置了intent-filter爲Main的activity。就是下面這個:

1
2
3
4
5
6
7
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

而作爲library的時候是不能有入口activity的。

這個就是組件化的具體實現,但是這樣會有一個問題就是如果組件的數量過多的話,我們管理起來也是一件麻煩事,想象下你不停地重複上述的操作的情景。所以我們需要把這一個過程交給gradle腳本,實現自動化構建。

使用Gradle實現自動化構建

自動化構建這個聽起來很厲害的概念實際上是十分簡單的,我們直入主題。我們先開口項目根目錄下的gradle.properties文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Turn on debugging
isDebug=true
# Deps for gradle
BUILD_TOOLS_VERSION=25.0.0
COMPILE_SDK_VERSION=25
# SDK versions for the samples
MIN_SDK_VERSION=15
TARGET_SDK_VERSION=25
# Deps for libraries
GSON_VERSION=2.7
FRESCO_VERSION=0.9.0
# App Version
VERSION_CODE=1
VERSION_NAME=1.0.0

在這個文件中,我定義了一些全局變量。這些全局變量的作用是同一所有模塊中使用的android sdk的版本和一些庫的版本,在這裏我們只看isDebug這個變量就可以了。isDebug爲true的時候我們的所有業務組件的屬性就設置爲application屬性,爲false的時候就設置爲library屬性即可。至於如何解決Manifest中的入口問題,我們先來看看test組件的項目結構:
test組件接口

就是通過創建兩個不同的Manifest文件解決的,這兩個文件的區別就是debug中定義了入口activity。如果調用這個模塊需要初始數據的話,我們創建一個DebugActivity就可以了。例如電商例子中,我們需要一個可以輸入商品號添加商品到購物車的DebugActivity。這裏需要注意的是,購物車功能需要登錄才能使用,我們使用debug模式的時候需要在自定義Application中提供一個debugKey來讓提供校驗密鑰。

下面我們看看test模塊中的build.gradle的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
if (isDebug.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
if (isDebug.toBoolean() ){
applicationId "com.example.test"
}
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
if (isDebug.toBoolean()){
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
dependencies {
compile project(':BaseLibraries:BaseMvpLib')
}

在這裏只需要關注兩個地方就可以,第一個就是根據isDebug的值來確定test組件的屬性。第二個就是在sourceSets中根據isDebug的值來選擇Manifest文件。
當test屬性爲application屬性時,app Module是無法依賴test的,所以app的build.gradle文件需要根據isDebug的值來選擇是否以來test組件。下面是具體實現邏輯:

1
2
3
4
5
6
7
8
dependencies {
if(isDebug.toBoolean()){
compile project(':BaseLibraries:BaseApp')
}else{
compile project(":Business:test")
}
}

對於第三章中讀者提出的問題的幾個解決思路

第三章中,讀者提出來了一個好問題,這個問題對於我們在使用MVP模式進行開發的時候是無法迴避的。相信很多讀者在看介紹MVP模式的文章的時候都會看到一個問題就是:當業務代碼越來越複雜的時候Presenter會變得越來越臃腫。所以在這裏,筆者採用的解決方案就是一個Presenter對應一個接口的方法來解決。
當採用筆者的解決方法的時候,如果同一個頁面有多個Api接口的話,我們只需要在Activity / Fragment中實現多個IView接口和實例化多個Presenter就可以了。這樣做的優點就是,我們的接口就好像是一塊塊積木一樣,當我們需要使用的時候某個接口的時候,通過Presenter就可以很簡單的在Activity中使用。這種方案的缺點是當App的體積到達一定的程度後Presenter的數量會很多。我們可以通過組件化的方式降低這個問題帶來的影響。
還有一種方案就是,一個頁面對應一個Presenter。這個方案有兩個思路,一個是:Model負責控制多個Api接口,一個Api接口對應一個發起請求的方法。然後在Presenter中根據實際接口的情況來創建回調方法。另一種是,一個Presenter中控制多個Model然後在需要的時候使用不同的Model向服務器獲取數據即可。這種方法的缺點是代碼的耦合度會比較高,當頁面中的接口過多的時候,Presenter會非常臃腫,並且無法在其他頁面中複用這個Presenter接口。當我們某個頁面中需要使用同一個接口的時候,我們必須重新實現一次。
筆者認爲,如果我們開發的app體量不是很大的時候,我們可以使用第二種方案進行開發。如果APP體積比較大的時候,通過筆者提供的解決方法再配合組件化的項目構建方案能夠應對更復雜的實際場景。所以需要採用何種方案就需要讀者自行判斷了。

小結

關於介紹MVP框架的文章就到這裏結束了,在這裏是時候接到讀者提出的問題了。
當我們使用MVP模式的時候Presenter文件的數量問題是無法解決的,我們只能通過其它的方法來增強我們對於Presenter的管理。相信讀者也看出來了,這個解決方法和編寫代碼的關係並不大。這個方法的思想是使用組件化的方法來構建我們的項目實現各組件間高內聚低耦合的目的。
在這個篇文章中,我們所寫的代碼並不多,但是卻解決了Presenter管理、模塊化開發、單模塊測試、降低項目耦合度等問題。所以這裏也給讀者一些建議,就是在解決問題的時候,我們要跳出我們的常規思維,有時候只需要做一小點改變結果就會大大不同。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章