一、模塊化淺談
1. 什么是模塊化開(kāi)發(fā)派敷?
模塊化就是將一個(gè)程序按照其功能做拆分癌压,分成相互獨(dú)立的模塊仰泻,以便于每個(gè)模塊只包含與其功能相關(guān)的內(nèi)容。比如登錄功能可以是一個(gè)模塊滩届。
2. 模塊化開(kāi)發(fā)的優(yōu)勢(shì)集侯?
-
解耦性強(qiáng):隨著業(yè)務(wù)的增多,代碼變的越來(lái)越復(fù)雜帜消,每個(gè)模塊之間的
代碼耦合變得越來(lái)越嚴(yán)重棠枉,解耦問(wèn)題急需解決。 - 編譯時(shí)間大大減少:以為業(yè)務(wù)場(chǎng)景對(duì)泡挺,代碼越來(lái)越大辈讶,同時(shí)編譯時(shí)間也會(huì)越來(lái)越長(zhǎng)。
- 提高團(tuán)隊(duì)協(xié)同開(kāi)發(fā):團(tuán)隊(duì)協(xié)同開(kāi)發(fā)存在較多的沖突.不得不花費(fèi)更多的時(shí)間去溝通和協(xié)調(diào),影響開(kāi)發(fā)效率 娄猫。
二贱除、模塊化開(kāi)發(fā)的架構(gòu)分層
本項(xiàng)目是一個(gè)移動(dòng)端的后臺(tái)運(yùn)營(yíng)管理系統(tǒng)。按照功能劃分媳溺,暫有統(tǒng)計(jì)模塊月幌、直播模塊、音頻模塊和個(gè)人中心悬蔽。
命名建議:
底層:Library
中間層:Module + 業(yè)務(wù)或功能名字
上層:App + 項(xiàng)目名字
1. 基本結(jié)構(gòu)
app模塊是每個(gè)項(xiàng)目初始都有的扯躺,實(shí)質(zhì)上仍然是一個(gè)module。Android Stuido項(xiàng)目中屯阀,我們和代碼打交道的大部分時(shí)間都在module中缅帘。當(dāng)然,具體的module機(jī)制我們不去深究难衰,有時(shí)間可以另開(kāi)一坑去研究。下面的三個(gè)module大家可以理解為kotlin中的library逗栽,當(dāng)然這樣說(shuō)不準(zhǔn)確盖袭。
app作為主要的module,主要承擔(dān)了應(yīng)用啟動(dòng)以及最上層的通用邏輯。比如在這個(gè)例子中鳄虱,應(yīng)用啟動(dòng)弟塞、首頁(yè)展示和模塊切換的業(yè)務(wù)邏輯都在這個(gè)模塊。
而下屬的三個(gè)子模塊承擔(dān)了各自細(xì)分的業(yè)務(wù)拙已,并且可以被app模塊或者其他模塊引用决记,每個(gè)模塊只負(fù)責(zé)和考慮自己的業(yè)務(wù)。以此達(dá)到業(yè)務(wù)邏輯和代碼分離的邏輯倍踪。
按照之前的業(yè)務(wù)邏輯劃分系宫,項(xiàng)目結(jié)構(gòu)可以先大致分為如下的結(jié)構(gòu):
2. 系統(tǒng)層分離
僅僅這樣是不夠的。因?yàn)樵贏S中建车,module的引用是單向的扩借。如果A module引用了B module,那么對(duì)A來(lái)講缤至,B是可見(jiàn)的潮罪,B的所有公開(kāi)功方法理論上都可以在A中使用,但是對(duì)B來(lái)說(shuō)领斥,A是不可見(jiàn)的嫉到。所以,這樣的結(jié)構(gòu)出現(xiàn)的問(wèn)題就是我們有大量的底層通用方法都放在app模塊中月洛,對(duì)于子模塊來(lái)講是不可見(jiàn)的何恶,子模塊無(wú)法引用封裝好的底層方法,例如網(wǎng)絡(luò)請(qǐng)求膊存,圖片加載导而,文件拷貝等,這肯定是不行的隔崎。所以這個(gè)結(jié)構(gòu)還得再優(yōu)化今艺。
按照module單向引用的原則,我們可以把公共底層通用方法單獨(dú)分出來(lái)作為一個(gè)moduleBase爵卒,同時(shí)這個(gè)moduleBase也是最底層的module虚缎,保證對(duì)于其他module來(lái)說(shuō)它都是可見(jiàn)的。這樣還有一個(gè)好處就是這個(gè)moduleBase中的方法和配置大部分都是可以高度復(fù)用的钓株。在新開(kāi)其他項(xiàng)目的時(shí)候這個(gè)模塊可以直接遷移到新項(xiàng)目中实牡,而不用在為代碼分離和剔除浪費(fèi)時(shí)間。所以轴合,項(xiàng)目的結(jié)構(gòu)進(jìn)一步細(xì)化成了如下的結(jié)構(gòu):
3. 公共層分離
上述的模塊如果在使用中创坞,有很大概率會(huì)遇到一個(gè)問(wèn)題,部分的實(shí)體類(lèi)受葛、自定義view题涨、布局文件或者資源文件在各個(gè)模塊都需要用到偎谁,但是這些如果放在系統(tǒng)層的moduleBase里面,又會(huì)破壞系統(tǒng)層的通用性纲堵。所以巡雨,我們還需要一個(gè)公共層Provider來(lái)專(zhuān)門(mén)提供上層業(yè)務(wù)邏輯模塊的公共資源。直接上圖:
4. 擴(kuò)展
待完善...
三席函、如何進(jìn)行安卓模塊化開(kāi)發(fā)
1. 創(chuàng)建Module模塊
將所需的模塊在對(duì)應(yīng)項(xiàng)目的Project目錄下拷貝出來(lái)铐望,粘貼到要開(kāi)發(fā)的項(xiàng)目的Project根目錄下即可。也可以直接在project下新建一個(gè)Module茂附。
File --> New --> New Module --> Android Library (建議選擇這個(gè)) --> Finish
一個(gè)模塊這樣就創(chuàng)建完成了正蛙。默認(rèn)的名字是app,根據(jù)項(xiàng)目的需要將模塊的名字進(jìn)行修改何之,直接Refactor > Rename...即可
2. 將Module模塊引入主項(xiàng)目中
設(shè)置setting.gradle 中
include ':app', ':UserCenter'
設(shè)置后發(fā)現(xiàn)項(xiàng)目目錄下增加了一個(gè)模塊
在主模塊的build.gradle中設(shè)置
api project(':UserCenter')
3. 模塊中application和library狀態(tài)切換配置
1. 設(shè)置一個(gè)開(kāi)關(guān)控制application和library狀態(tài)切換
我們?cè)陂_(kāi)發(fā)的時(shí)候跟畅,Module如果是一個(gè)庫(kù),會(huì)使用com.android.library插件溶推,如果是一個(gè)應(yīng)用徊件,則使用com.android.application插件,接下來(lái)根據(jù)這個(gè)變量來(lái)進(jìn)行判斷并且實(shí)現(xiàn)狀態(tài)切換蒜危。
Project根目錄下gradle.properties中設(shè)置變量來(lái)控制虱痕。
isUserModule = false
isUserModule=false:表示這個(gè)模塊是一個(gè)UserCenter的Module;
isUserModule=true:表示這個(gè)模塊是一個(gè)app;
2. 依賴項(xiàng)目中build.gradle配置
在模塊的build.gradle的開(kāi)頭處設(shè)置。
//頂部插件引入
if(isUserModule.toBoolean()){
apply plugin: 'com.android.library'
}else {
apply plugin: 'com.android.application'
}
android {
...
sourceSets{
main{
if (isUserModule.toBoolean()){
manifest.srcFile 'src/main/release/AndroidManifest.xml'
//release模式下排除debug文件夾中的所有Java文件
java{
exclude 'debug/**'
}
}else{
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
3. 提供兩套 AndroidManifest.xml并進(jìn)行動(dòng)態(tài)切換
mainfest文件也需要提供兩套
android {
...
sourceSets{
main{
if (isUserModule.toBoolean()){
manifest.srcFile 'src/main/release/AndroidManifest.xml'
//release模式下排除debug文件夾中的所有Java文件
java{
exclude 'debug/**'
}
}else{
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
四辐赞、Kotlin模塊化開(kāi)發(fā)過(guò)程中遇到的問(wèn)題
1. 資源名稱沖突
Android Studio 默認(rèn) library 的所有的 resource 為 public , 所以在模塊化開(kāi)發(fā)過(guò)程中總會(huì)遇到資源沖突問(wèn)題部翘。列出兩種解決方法。
-
方法一:
保護(hù)某些 resources 不被外部訪問(wèn)响委,可以創(chuàng)建res/values/public.xml新思,因?yàn)?public 是關(guān)鍵詞,搜易需要用 new file 的方式創(chuàng)建赘风。至少添加一行夹囚,為添加的視為 private
<resources>
<public name="mylib_app_name" type="string"/>
</resources>
-
方法二:
在 library 的 build.gradle 中添加 resourcePrefix , 則所有的資源須以此 prefix 開(kāi)頭,否則報(bào)錯(cuò)邀窃。注意荸哟,圖片資源雖然不提示報(bào)錯(cuò)誤,但是也需要修改名字瞬捕。
android {
...
buildTypes {
...
}
resourcePrefix 'my_prefix_'
}
2.重復(fù)依賴
將所有的依賴都寫(xiě)在library層(BaseLibrary鞍历、Provider)的module,將所有的依賴統(tǒng)一成一個(gè)入口給上層的app去引用肪虎。
3.控制臺(tái)報(bào)錯(cuò)解決
Error:Module 'qsp_release:libLive:unspecified' depends on one or more Android Libraries but is a jar
報(bào)這個(gè)錯(cuò)誤的場(chǎng)景劣砍,Moudle A 的build.gradle下
apply plugin: 'java'
...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':library')
...
}
可以看出 Moudle A 是一個(gè)jar形式的依賴模塊,而library是apply plugin: 'com.android.library'所以引用報(bào)如上錯(cuò)誤扇救。
4. 使用Dagger2時(shí)秆剪,需要分別在app層級(jí)下的build.gradle中添加如下代碼
apply plugin: 'kotlin-kapt'//kapt 插件
dependencies {
//Dagger2
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
5. implementation赊淑、api與多模塊依賴
自從gradle升級(jí)3.+版本后爵政,gradle原來(lái)的依賴方法全部都被替換了仅讽,之前的compile替換成了implementation和api,新建工程時(shí)發(fā)現(xiàn)gradle默認(rèn)使用的也是implementation钾挟。
其中 api 與之前的 compile 功能基本一致洁灵,不再贅述;implementation 就比較高級(jí)了掺出,其作用就是徽千,使用 implementation 添加的依賴不會(huì)再編譯期間被其他組件引用到,但在運(yùn)行期間是完全可見(jiàn)的汤锨。這也是一種代碼隔離双抽。舉個(gè)例子:
組件A依賴lib1,既A implementation lib1
組件B依賴組件A闲礼,既B api A
在 gradle3.0.0 之前牍汹,B是完全可以引用到 lib1 里面的類(lèi)的,但是現(xiàn)在B在編譯期間就做不到了柬泽,只能在運(yùn)行期可以慎菲。
在kotlin中,為了避免以后依賴庫(kù)引用無(wú)效的悲劇發(fā)生 锨并,盡量用 implementation ,只有要用到依賴中的依賴,再用api
6. compileDebugKotlin 解決方案之一(api錯(cuò)用導(dǎo)致的重復(fù)引用)
首先先補(bǔ)下 依賴的知識(shí) ,依賴可以用2種方式implementation和 api,舉例場(chǎng)景:
app 依賴 bModule , bModule依賴cModule
api:app既可以引用bModule的方法和類(lèi)又可以引用cModule中代碼
implementation: app能引用bModule中的方法和類(lèi) app不能引用cModule中的方法 這樣的好處是提高編譯效率
我項(xiàng)目的依賴關(guān)系是:
app api bModule
app implementation cModule
導(dǎo)致 重復(fù)引用cModule露该,一直報(bào)錯(cuò)compileDebugKotlin
解決辦法就是app implementation bModule
為了避免以后這種悲劇發(fā)生 盡量只用 implementation ,只有要用到依賴中的依賴,再用api