當(dāng)項(xiàng)目大到一定程度后,公司各個(gè)模塊的業(yè)務(wù)相互耦合,維護(hù)的時(shí)候非常困難,另外項(xiàng)目大到一定程度后,編譯速度也是個(gè)坑爹的東西,所以這個(gè)時(shí)候模塊化就顯得很有必要了。最近公司一直在推進(jìn)這方面的工作,于是我也從gayhub上fork了一個(gè)項(xiàng)目動(dòng)手實(shí)踐起來(lái),下面做個(gè)簡(jiǎn)單的記錄。
先貼代碼,對(duì)著代碼看更直觀:示例代碼
1.模塊劃分
模塊化的第一步就是模塊劃分要明確,否則所謂的模塊化無(wú)從談起。由于本項(xiàng)目直接fork自 https://github.com/BaronZ88/ModularizationProject ,所以直接偷了他的模塊劃分圖來(lái):
可以很明顯的看到整個(gè)模塊分為三層,基礎(chǔ)組件層,基礎(chǔ)業(yè)務(wù)層,業(yè)務(wù)層。最底下一層包含一些第三方開(kāi)源庫(kù)和公司自己開(kāi)發(fā)的各種底層庫(kù),在這一層中基本上通過(guò)gradle引入的第三方框架都在這里配置;第二層是基礎(chǔ)業(yè)務(wù)庫(kù),所謂的基礎(chǔ)業(yè)務(wù)庫(kù)就是有很多業(yè)務(wù)都可能涉及到這些的,比如一些公用的vo,比如登錄業(yè)務(wù),比如支付系統(tǒng);最高一層就是我們真正的模塊化的各種業(yè)務(wù),這一層的任何一個(gè)不同模塊都應(yīng)該可以單獨(dú)作為一個(gè)app跑起來(lái),每次開(kāi)發(fā)時(shí)應(yīng)該只需要運(yùn)行某一個(gè)模塊,從而避免影響其它業(yè)務(wù)。在我們的示例代碼中,結(jié)構(gòu)就是這樣的:
其中App模塊就是我們的主模塊,運(yùn)行這個(gè)模塊保證所有模塊都會(huì)被當(dāng)做module引入,然后其它以Module結(jié)尾的都可以單獨(dú)運(yùn)行起來(lái)。另外CommonBusiness和CoreModel是基礎(chǔ)業(yè)務(wù)層的模塊,OpenSourceLibrary是最底下一層基礎(chǔ)組件層的module。
有了這些基礎(chǔ),模塊化才能繼續(xù)進(jìn)行下去。
2.配置運(yùn)行不同的模塊
(1)區(qū)分兩種狀態(tài)
首先要明確一點(diǎn),對(duì)任意一個(gè)業(yè)務(wù)module來(lái)說(shuō),它都有兩種狀態(tài),一種是運(yùn)行App模塊時(shí)的module狀態(tài),單純提供依賴,一種是模塊單獨(dú)運(yùn)行時(shí)的可運(yùn)行狀態(tài)。
為了區(qū)分這兩種狀態(tài),我們?cè)趃radle.properties文件中添加了一個(gè)變量isBuildModule,當(dāng)為true的時(shí)候,各個(gè)業(yè)務(wù)模塊能夠單獨(dú)運(yùn)行,當(dāng)為false時(shí),每個(gè)業(yè)務(wù)module無(wú)法單獨(dú)跑起來(lái)只能作為module引入,將整個(gè)應(yīng)用跑起來(lái),此時(shí)只有App模塊能運(yùn)行。
(2)配置兩種狀態(tài)下的gradle文件
一個(gè)module是否可以單獨(dú)運(yùn)行,是由它的gradle文件中的配置決定的,該module如果只是一個(gè)library,通常我們這樣寫:
apply plugin: 'com.android.library'
但如果要讓該module能單獨(dú)跑起來(lái)我們要這么寫:
apply plugin: 'com.android.application'
綜合一下,結(jié)合isBuildModule變量,最終我們這么處理:
if (isBuildModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
這樣,我們就能夠通過(guò)修改isBuildModule來(lái)配置是否單獨(dú)運(yùn)行模塊。
(3)提供兩套AndroidManifest.xml文件
由于一個(gè)module要想單獨(dú)跑起來(lái),那么我們必須要指定啟動(dòng)的Activity,但是當(dāng)isBuildModule為false時(shí)又不需要,所以我們要準(zhǔn)備兩套Manifest文件。以NewHouseModule為例:
Manifest文件分為debug模式和release模式,debug模式就是該模塊可以單獨(dú)運(yùn)行的模式。然后在該module的gradle文件中配置下:
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
這里分別看下兩種文件的區(qū)別:
debug:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.baronzhang.android.newhouse">
<application
android:allowBackup="true"
android:icon="@mipmap/new_house_ic_launcher"
android:label="@string/new_house_app_name"
android:supportsRtl="true"
android:theme="@style/NewHouseAppTheme">
<activity
android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
android:label="@string/new_house_label_home_page">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
release:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.baronzhang.android.newhouse">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity
android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
tools:replace="label"
android:label="@string/new_house_label_home_page">
</activity>
</application>
</manif
這里差距主要是是否配置了啟動(dòng)Activity。
(4)配置依賴關(guān)系
經(jīng)過(guò)上面三部的配置,按道理可以通過(guò)修改isBuildModule變量來(lái)配置是否讓某個(gè)模塊單獨(dú)運(yùn)行了,然后事實(shí)不是這樣,當(dāng)isBuildModule為true時(shí),這個(gè)時(shí)候運(yùn)行App模塊會(huì)報(bào)錯(cuò),我們需要修改下App模塊下的gradle文件:
if (isBuildModule.toBoolean()) {
compile project(':CoreModel')
compile project(':CommonBusiness')
} else {
compile project(':InstantMessagingModule')
compile project(':NewHouseModule')
compile project(':SecondHouseModule')
}
當(dāng)運(yùn)行的是某個(gè)模塊是,每一個(gè)業(yè)務(wù)module都是一種application,而不是library,是不能以compile project的方式引入的,所以要排除掉,但也同時(shí)排除了這些module依賴的一些基礎(chǔ)業(yè)務(wù)庫(kù),所以我們要引入進(jìn)來(lái)。這樣,配置每個(gè)模塊單獨(dú)運(yùn)行就完成了。
3.配置路由跳轉(zhuǎn)
由于不同的業(yè)務(wù)相互解耦的非常徹底,然后有些時(shí)候必然會(huì)出現(xiàn)一個(gè)模塊要跳往另一個(gè)模塊,這種時(shí)候,系統(tǒng)自帶的跳轉(zhuǎn)已經(jīng)不行了,因?yàn)樵撃K中沒(méi)有另一個(gè)模塊的Activity,這里可以考慮通過(guò)Scheme跳轉(zhuǎn),但一來(lái)配置麻煩,二來(lái)還有安全風(fēng)險(xiǎn),所以不推薦。這個(gè)時(shí)候,Android頁(yè)面路由就該上場(chǎng)了。
原作者自己擼了一個(gè)頁(yè)面路由框架,我等渣渣只能膜拜,然后自己去github上找了下,最終選定了阿里巴巴開(kāi)源的ARouter,配置簡(jiǎn)單,上手簡(jiǎn)單,使用簡(jiǎn)單。該框架的詳細(xì)使用教程看這里:https://github.com/alibaba/ARouter 。這一步?jīng)]有難度,照著文檔來(lái)就行了,注意一點(diǎn),凡是使用ARouter的模塊,都要在build.gradle文件中加入
dependencies {
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
這里更推薦的是將 compile 'com.alibaba:arouter-api:x.x.x' 放到OpenSourceLibrary模塊中去,每個(gè)模塊只需要配置annotationProcessor即可。
總結(jié)
經(jīng)過(guò)以上操作,最簡(jiǎn)單的模塊化就完成了,后面會(huì)繼續(xù)介紹一些稍微麻煩一些的內(nèi)容:
Android模塊化之登錄業(yè)務(wù)處理
Android模塊化之ButterKnife和Dagger2的使用
參考文檔:
Android 模塊化探索與實(shí)踐