為什么模塊化
隨著業(yè)務(wù)模塊不斷增加,業(yè)務(wù)線的增多,功能也越來越多。雖然項(xiàng)目按業(yè)務(wù)或者功能分包,但是隨著代碼的增多,代碼結(jié)構(gòu)不清晰;每個(gè)模塊之間的代碼耦合變得越來越嚴(yán)重,解耦問題急需解決;同時(shí)編譯時(shí)間也會(huì)越來越長(zhǎng),打包速度變慢。如果再加上App各個(gè)業(yè)務(wù)模塊比較獨(dú)立和清晰,這就促使了項(xiàng)目走上模塊化開發(fā)。這也是我們團(tuán)隊(duì)當(dāng)初要模塊化的原因,經(jīng)過改造一個(gè)版本,到最后的穩(wěn)定上線。模塊化的過程中,遇到的問題比較多,細(xì)節(jié)也比較多,在此只是簡(jiǎn)單分享下模塊化的大致步驟和流程。
如何模塊化
1:整體項(xiàng)目大致結(jié)構(gòu)

| 注解 | 解釋 |
|---|---|
| Shell App | App殼工程,負(fù)責(zé)管理各個(gè)業(yè)務(wù)模塊,以及主工程的打包。 |
| moudlea,moduleb,modulec…… | 不同代表不同的業(yè)務(wù)模塊,也可以是熱更新業(yè)務(wù),可以單獨(dú)運(yùn)行,調(diào)試,也可以在外殼App中集成模式運(yùn)行。 |
| basecommonbusiness | 不同業(yè)務(wù)模塊抽出公共的業(yè)務(wù)邏輯,以及定義的各種基類,例如basemoduleactivity,baseapplication,arouter路由配置。 |
| commontoollib | 基礎(chǔ)模塊,各個(gè)業(yè)務(wù)實(shí)現(xiàn)需要的基礎(chǔ)工具類 |
| basecorelib | 功能模塊,第三方組件rn,library,sdk,aar組件例如networklib,downloadlib,burypointlib,paylib等 |
| baseuilib | theme,shape,color,一些公用的控件,例如加載刷新控件,自定義的dialog等 |
| CI | 持續(xù)集成工具(jenkins) |
2:如何設(shè)置模塊模式,集成模式調(diào)試和運(yùn)行
gradle.properties
殼App的build.gradle和module中build.gradle配置
首先通過gradle.properties文件,定義isModule布爾變量,來控制是否開啟模塊單獨(dú)運(yùn)行和集成運(yùn)行的開關(guān)。如果是一個(gè)獨(dú)立運(yùn)行的項(xiàng)目,build.gradle中apply plugin需要是application并且applicationId,以及AndroidManifest會(huì)有相關(guān)的配置,所以在殼app和各個(gè)業(yè)務(wù)module中根據(jù)isModule來控制這些。
if(isModule.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
android {
compileSdkVersion build_versions.compileSdkVersion
defaultConfig {
if(isModule.toBoolean()) {
applicationId "com.zzti.gank.modulea"
multiDexEnabled true
}
minSdkVersion build_versions.min_sdk
targetSdkVersion build_versions.target_sdk
versionCode 1
versionName "1.0"
resourcePrefix "modulea_"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
flavorDimensions "1"
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'debug/**'
}
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation deps.support.app_compat
implementation deps.constraint_layout
testImplementation 'junit:junit:4.12'
androidTestImplementation deps.runner
androidTestImplementation deps.espresso.core
api deps.arouter.runtime
annotationProcessor deps.arouter.compiler
api project(':basecommonbusiness')
}
3:如何解決模塊之間依賴沖突,資源沖突
依賴沖突
在模塊化的過程中,最常見的應(yīng)該是依賴沖突。由于團(tuán)隊(duì)成員之間sdk更新版本的不同,gradle插件版本不同,這樣成員在提交代碼后,其他成員拉取后,可能需要不斷的更新sdk或者gradle插件,甚至提交的殼app的gradle插件和持續(xù)開發(fā)工具(例如jenkins)配置的不一樣,這樣會(huì)導(dǎo)致打包失敗。除此之外還有一些jcenter引入的三方庫沖突,以及第三方庫中,同樣存在相同的依賴,但是版本不同,這時(shí)候需要用exclude group排除下相同依賴。如何控制版本管理?這時(shí)候需要新建一個(gè)version.gradle文件,統(tǒng)一處理jcenter引入的三方庫,分組或者單一的統(tǒng)一版本信息,配置compileSdkVersion、buildToolsVersion、minSdkVersion、targetSdkVersion版本管理,以及sdk版本和gradle的插件版本等。如果你熟悉Groovy語言,對(duì)在gradle里面的配置就可以輕車熟路。
def versions = [:]
versions.android_gradle_plugin = "3.1.0"
versions.support = "27.1.1"
versions.constraint_layout = "1.1.3"
versions.junit = "4.12"
versions.runner = "1.0.2"
versions.espresso = "3.0.2"
versions.arouter_version = "1.2.4"
versions.arouter_processor_version = "1.1.4"
def build_versions = [:]
build_versions.compileSdkVersion = 27
build_versions.build_tools= "27.0.3"
build_versions.min_sdk = 15
build_versions.target_sdk = 27
build_versions.versionCode = 100
build_versions.versionName = "1.0.0"
build_versions.versionsupport = "27.1.0"
ext.build_versions = build_versions
def deps = [:]
deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin"
deps.junit = "junit:junit:$versions.junit"
deps.constraint_layout = "com.android.support.constraint:constraint- layout:$versions.constraint_layout"
deps.runner = "com.android.support.test:runner:$versions.runner"
def support = [:]
support.app_compat = "com.android.support:appcompat-v7:$versions.support"
support.annotations = "com.android.support:support- annotations:$versions.support"
deps.support=support
def espresso = [:]
espresso.core = "com.android.support.test.espresso:espresso- core:$versions.espresso"
espresso.contrib = "com.android.support.test.espresso:espresso-contrib:$versions.espresso"
espresso.intents = "com.android.support.test.espresso:espresso-intents:$versions.espresso"
deps.espresso = espresso
def arouter = [:]
arouter.runtime = "com.alibaba:arouter-api:$versions.arouter_version"
arouter.compiler = "com.alibaba:arouter- compiler:$versions.arouter_processor_version"
deps.arouter = arouter
ext.deps = deps
def addRepos(RepositoryHandler handler) {
handler.maven {
url 'https://maven.google.com/'
name 'Google'
}
handler.jcenter()
handler.mavenCentral()
}
ext.addRepos = this.&addRepos
資源沖突
不同模塊對(duì)于資源的命名可能會(huì)有沖突,為了防止不同模塊的資源應(yīng)為命名沖突而被錯(cuò)誤的覆蓋,就需要一種機(jī)制能夠檢查、提示、修改沖突的資源,因此需要在每一個(gè)業(yè)務(wù)module的build.gradle中添加resourcePrefix。如果這個(gè)模塊里面存在自定義view或者自定義屬性,它們都需要添加resourcePrefix的字段。
4:模塊如何通信
模塊間通信引入阿里ARouter,用來處理配置所有module的跳轉(zhuǎn)路由,各個(gè)業(yè)務(wù)module之前的跳轉(zhuǎn),參數(shù)傳遞,解析參數(shù),Interceptor 攔截器等,具體參考github地址ARouter ,至于模塊之間的數(shù)據(jù)更新通知可以使用復(fù)雜的廣播形式,或者EventBus
5:相關(guān)問題處理
模塊化中遇到各種類型問題,在此抽幾個(gè)典型的問題分享下。
靜態(tài)常量問題
正常app項(xiàng)目在編譯過程中,會(huì)生成R.java文件。R文件中包含有各個(gè)資源文件對(duì)應(yīng)的id,這些變量是final類型的,但是在Library Module中,這些id不是final類型的。由于switch、case和第三方ButterKnife都需要靜態(tài)常量,所有就會(huì)出現(xiàn)問題。因此子moudle中onClick的回調(diào)不可以使用switch、case,必須要用if else來的代替。至于ButterKnife,在ButterKnifeV8.4.0 之后修復(fù)了這個(gè)問題,在Library Module中,生成一個(gè)R2.java文件,在R2中各個(gè)id都是靜態(tài)常量。所以在來回切項(xiàng)目時(shí)候,需要將R更換R2,這時(shí)候可以通過全局映射替換。
buildTypes設(shè)置
項(xiàng)目中會(huì)有多種buildTypes的設(shè)置,這時(shí)候殼App的buildTypes有多少,在每個(gè)子module的配置中也得保持一致。子module中不一定需要具體的配置,但是得保證每個(gè)都得有,不然會(huì)導(dǎo)致運(yùn)行失敗。
依賴管理
在沒有模塊化之前,編譯整個(gè)項(xiàng)目或者調(diào)試自己負(fù)責(zé)的業(yè)務(wù)模塊,編譯速度會(huì)特別慢,導(dǎo)致效率低下,如果在模塊化之后,可以讓團(tuán)隊(duì)成員每人去拉取自己負(fù)責(zé)的業(yè)務(wù)模塊,各個(gè)模塊單獨(dú)打包,調(diào)試,子模塊開發(fā)完成以后,確定后版本后,發(fā)布到公司的私有maven倉庫中,然后在主項(xiàng)目中使用版本進(jìn)行依賴,提升開發(fā)效率。
模塊化帶來優(yōu)勢(shì)
a項(xiàng)目編譯運(yùn)行速度提高,提高效率
b代碼規(guī)范,結(jié)構(gòu)清晰,規(guī)范化管理,沉淀團(tuán)隊(duì)技術(shù)
c各模塊業(yè)務(wù)獨(dú)立清晰,新人較短時(shí)間熟悉項(xiàng)目很快入手
d部分模塊可以嘗試引用新技術(shù),方便技術(shù)更新
寫在最后
demo沒有具體功能實(shí)現(xiàn),但依舊按江湖規(guī)矩給出連接component