轉(zhuǎn)載于:https://juejin.cn/post/7142297951023415332 寫的挺好呀,學(xué)習(xí)下
組件化,在實際的業(yè)務(wù)開發(fā)中,越來越多的會使用這種方式,特別是業(yè)務(wù)邏輯復(fù)雜,功能模塊較多的項目,越能凸顯出組件化的優(yōu)點,比如各個模塊拆分,使其業(yè)務(wù)分明,比如耦合度低,組件之間相互獨立,再比如編譯運行速度大大降低,還有代碼復(fù)用,減少代碼冗余,責(zé)任明確,減少合并沖突等等,可謂是優(yōu)點多多,正因為有足夠多的優(yōu)點,組件化開發(fā),一直是目前項目開發(fā)中的所推崇的開發(fā)方式之一。
組件化方式的開發(fā),市面上已經(jīng)有很多的文章去闡述,而關(guān)于本章的內(nèi)容,在于總結(jié),在于有實際的組件化實戰(zhàn)代碼,有開源的組件化Demo樣例,重在淺顯易懂,重在能夠應(yīng)用于實際業(yè)務(wù),也重在簡單,希望能夠給大家?guī)硪唤z幫助。
本文會從以下四個模塊進行闡述,各位老鐵,準(zhǔn)備好板凳,我們開始。
1、為什么要采取組件化
2、如何實現(xiàn)組件化
3、組件化實戰(zhàn)
4、開源及Demo
溫馨提示,開源相關(guān)Demo地址,請滑至文末。
一、為什么要采取組件化
為什么要采取組件化的方式,這個在文章的開頭已經(jīng)訴述了部分優(yōu)點,當(dāng)然了,在這里再進行比較詳細的概述一下。
1、提高編譯運行速度
當(dāng)我們的項目隨著版本的不斷迭代,隨之增加的功能會越來越多,業(yè)務(wù)也會變得越來越復(fù)雜,最終會導(dǎo)致代碼量急劇上升,相關(guān)的三方sdk也會不斷的涌入,以至于,更改一處,就要全量編譯運行,有時候甚至?xí)霈F(xiàn),改一行而等10分鐘的情況,非常的耗時,大大降低了開發(fā)效率。
而采取了組件化的方式后,相關(guān)業(yè)務(wù)模塊,進行單獨抽取,使得每個業(yè)務(wù)模塊可以獨立當(dāng)做App存在,和其他模塊,互不關(guān)聯(lián)影響,在編譯運行時期,只考慮本模塊即可,從而減少了代碼的編譯量,提高了編譯運行速度,節(jié)約了開發(fā)時間。
2、業(yè)務(wù)拆解,完全解耦
單獨的業(yè)務(wù)模塊進行抽取成一個獨立的組件,也就是相互不關(guān)聯(lián)的Module,在各自的模塊中書寫相關(guān)的代碼,做到,業(yè)務(wù)拆解,人員拆解,實現(xiàn)真正的解耦。
3、功能復(fù)用,節(jié)約開發(fā)時間
所謂的功能復(fù)用,不僅僅是同項目之間的復(fù)用,更是以后同樣功能模塊的復(fù)用,比如A項目中有一個直播模塊,后面開發(fā)的B項目也有,完全可以移植過來復(fù)用,無非就是UI等簡單邏輯的修改。
4、責(zé)任明確,分工明確
組件化的項目,各個業(yè)務(wù)單獨成Module,在獨自的Module中開發(fā)相關(guān)的業(yè)務(wù)需求,相對于糅合到一個模塊中的項目來說,業(yè)務(wù)之間拆分更加明確,更加清晰,我負責(zé)哪個業(yè)務(wù),就去哪個組件下去寫,使得所負責(zé)的任務(wù)清晰明確,后續(xù)定位問題,也能夠第一時間發(fā)現(xiàn)并修改。
二、如何實現(xiàn)組件化
曉得了組件化的優(yōu)點之后,那么在實際的業(yè)務(wù)開發(fā)中,如何實現(xiàn)呢?首先做為組件化,必須相關(guān)業(yè)務(wù)拆分,單獨成一個Module,并且可以單獨的編譯運行,這是最起碼的一個前提,否則,就不能成為真正意義上的組件化,要實現(xiàn)組件化開發(fā),需要約束且需要的考慮的因素,大概總結(jié)如下。
1、代碼架構(gòu)拆分,合理規(guī)劃
一個項目從0到1的實施,少不了很多基礎(chǔ)的依賴,比如網(wǎng)絡(luò),比如圖片加載,比如一些第三方sdk等等,無論你的項目是否是組件化,這些潛在的前提,是必不可少的,可能不是組件化的項目,這些底層的使用會和業(yè)務(wù)相關(guān)的代碼放到一起,但在組件化的項目,基礎(chǔ)庫和業(yè)務(wù)層還是要進行剝離的。
首先呢,基礎(chǔ)的依賴是一層,這一層,包含了上層業(yè)務(wù)層所需要的必須實現(xiàn),比如網(wǎng)絡(luò)庫,比如圖片加載庫,比如Dialog加載庫,再比如一些常見的,工具類,數(shù)據(jù)操作等等,我們可以叫它為基礎(chǔ)庫,基礎(chǔ)庫的存在,除了提供必須的能力之外,更是對能力的封裝,封裝在于,給業(yè)務(wù)層提供方便的調(diào)用方式,更是為了可替換,也就是說,后續(xù)一旦升級或者更換其他的,直接在基礎(chǔ)庫中更改即可,業(yè)務(wù)層無需操作,大大減少耦合度,比如圖片加載,一開始我們使用的是Glide,后邊要調(diào)整為coil,只需要在基礎(chǔ)庫中更改即可。
基礎(chǔ)庫,在經(jīng)歷的封裝中,我是把每一個能力項,封裝成了一個Module,比如,網(wǎng)絡(luò)一個,圖片加載一個等等,這樣做的一個目的,是便于打aar包,也是便于業(yè)務(wù)層的調(diào)用。如下圖,是我曾經(jīng)的封裝,看起來很多,但只向業(yè)務(wù)層暴露依賴使用方式,畢竟這都是基礎(chǔ)能力,業(yè)務(wù)層是看不到的,當(dāng)然,這個看大家的實際業(yè)務(wù)需求。
[圖片上傳失敗...(image-ce9ad7-1663164225794)]
當(dāng)基礎(chǔ)庫滿足實際的開發(fā)需求之后,基礎(chǔ)庫就可以提供給業(yè)務(wù)層使用,可以以aar的方式,也可以以library的形式,我是比較推薦aar的方式,遠程依賴,方便業(yè)務(wù)層來調(diào)用,后期更改,業(yè)務(wù)層只需要更改版本號即可,對于基礎(chǔ)庫,可以逐個提供,也可以聚合提供。
逐個提供,就是把每個能力項,一個一個的給到業(yè)務(wù)層,讓業(yè)務(wù)層進行使用,比較好的一點是方便業(yè)務(wù)層選擇性使用,需要哪個就用哪個,不會造成內(nèi)存上的浪費。
聚合層提供,就是把所有的能力項糅合到一起,然后進行拓展,給到業(yè)務(wù)層,優(yōu)點是只依賴一個,簡單便捷。
具體的提供,主要還是看實際的業(yè)務(wù)需求,除了基礎(chǔ)庫之外,在實際的開發(fā)中,也就是業(yè)務(wù)層中,一般還存在一個中間層,中間層是基礎(chǔ)庫和業(yè)務(wù)層中間的紐帶,有著承上啟下的作用,一般公共的方法,屬性,資源,數(shù)據(jù)等都可以放到中間層。
具體的層次劃分,大家可以看下圖,當(dāng)然了,實際的業(yè)務(wù)需求,應(yīng)以本公司的為準(zhǔn)。
[圖片上傳失敗...(image-d03219-1663164225794)]
在層次劃分中,對于開發(fā)者來說,最重要的是業(yè)務(wù)模塊,這是直接和開發(fā)相關(guān)的,組件化的最直接表現(xiàn)就是這一層次,需要根據(jù)不同的業(yè)務(wù),來拆分出不同的模塊。
2、業(yè)務(wù)單獨拆分為Module,可單獨編譯運行
上邊已經(jīng)闡述,組件化的最直接表現(xiàn)就是在業(yè)務(wù)模塊,也就是根據(jù)項目的實際功能,拆分出符合的業(yè)務(wù)模塊,比如社區(qū),比如用戶信息,比如商城等等,拆分之后,一定要能保持單獨運行,本著合則依賴,拆則運行的態(tài)度,那么我們需要解決兩個問題,一個是application和library之間的動態(tài)切換,另一個就是需要動態(tài)改變清單文件資源指向,畢竟單獨運行的Module,必須有一個主入口的存在。
application和library之間的動態(tài)切換
我們都知道,一個項目在build.gradle中apply plugin只能存在一個application,也就是可運行的模塊,一般是我們的主模塊,是最終要產(chǎn)出apk包的,而對應(yīng)的library就是對應(yīng)的插件,一般以依賴的方式進行使用,在最終打包的時候進行依賴使用,而在單獨開發(fā)的時候就需要單獨運行。
如下舉例,根據(jù)動態(tài)參數(shù)來動態(tài)進行配置。
if(is_Module.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
復(fù)制代碼
同樣的,針對applicationId,每個業(yè)務(wù)組件,如果需要進行區(qū)分,也是需要進行判斷的。
if(is_Module.toBoolean()){
applicationId "com.abner.test"
}
復(fù)制代碼
動態(tài)改變清單文件資源指向
關(guān)于主入口,一個項目肯定只有一個,當(dāng)把組件改為單獨運行的時候,在組件的內(nèi)部,就不得不去創(chuàng)建一個屬于當(dāng)前組件的主入口,也就是,單獨運行時使用當(dāng)前組件的主入口,合并依賴運行時使用主Module里的主入口。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
復(fù)制代碼
動態(tài)切換清單文件,也是根據(jù)參數(shù)來動態(tài)進行配置。
sourceSets {
main {
if (is_Module.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/mainfest/AndroidManifest.xml'
}
}
}
復(fù)制代碼
關(guān)于業(yè)務(wù)層的Application,也就是初始化配置的入口,當(dāng)前組件下的mainfest引入即可,通過以上的配置,只需要改變動態(tài)參數(shù)值就可以實現(xiàn),單Module的運行。
3、各模塊統(tǒng)一依賴,統(tǒng)一版本號
隨著業(yè)務(wù)功能的不斷增加,相關(guān)的組件也會隨著增加,如果各個組件,都進行單獨依賴,一旦依賴多了之后,就會出現(xiàn)重復(fù)依賴,版本號不一致的情況,針對這種問題,最直接的處理方式是,統(tǒng)一gradle文件,也就是所有的組件統(tǒng)一使用一個,除了一些特殊的依賴之外,其他的都放在統(tǒng)一的文件之中。
gradle文件抽取封裝之后,便于所有的依賴管理,也便于統(tǒng)一版本號依賴。如下所示,抽取之后,每個組件下的build.gradle文件里,直接使用統(tǒng)一的文件即可,在代碼上也是非常的清晰簡單。
app下
[圖片上傳失敗...(image-f3b57d-1663164225794)]
其它Module下
[圖片上傳失敗...(image-ee2ffa-1663164225794)]
4、考慮問題一,組件之間頁面跳轉(zhuǎn)
由于組件之間互不依賴,一個突出的問題就是,頁面之間如何跳轉(zhuǎn),比如我A模塊要跳轉(zhuǎn)到B模塊中的一個頁面,我該如何跳轉(zhuǎn)呢?
關(guān)于組件間跳轉(zhuǎn),這個市場上已經(jīng)有很多成熟的三方了,比如ARouter ,ActivityRouter,DeepLinkDispatch等等,當(dāng)然用的比較多的就是ARouter了,已經(jīng)有了成熟的,我們直接使用即可,沒有必要再重復(fù)的造輪子。
通過ARouter 可以很方便的實現(xiàn)組件之間的通信,更重要的是方便了H5和原生交互之間的跳轉(zhuǎn)。
官方文檔如下
”一個用于幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦“,這是github 上 ARouter 的相關(guān)介紹,可以知道,它可以實現(xiàn)組件間的路由功能,路由是指從一個接口上收到數(shù)據(jù)包,根據(jù)數(shù)據(jù)路由包的目的地址進行定向并轉(zhuǎn)發(fā)到另一個接口的過程,這里可以體現(xiàn)出路由跳轉(zhuǎn)的特點,非常適合組件化解耦。
使用起來也是非常的簡單
ARouter.getInstance().build("/test/test").navigation()
復(fù)制代碼
具體使用,看大家可以查看官網(wǎng),而關(guān)于實際的業(yè)務(wù)使用,我們放到下面的第3項組件化實戰(zhàn)中。
5、考慮問題二,組件之間數(shù)據(jù)傳遞
數(shù)據(jù)之間的傳遞,如果是頁面單純的傳遞數(shù)據(jù),直接可以使用ARouter提供的傳遞方式,比如:
ARouter.getInstance().build("/test/test")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
復(fù)制代碼
如果不是頁面之間的傳遞,那么就需要我們自己定義接口或者通過中間層common來實現(xiàn)了,當(dāng)然了需要進行邏輯處理。
6、考慮問題三,組件之間Fragment使用
A組件要想使用B組件里的Fragment,我們可以通過反射的方式進行獲取,既然有了ARouter,我們直接可以使用ARouter提供的方式,非常的簡單。
fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation()
復(fù)制代碼
7、考慮問題四,組件之間功能互相調(diào)用
A組件想要觸發(fā)B組件里一個功能,一個事件,那么如何處理呢?這個可以利用ARouter的IProvider,具體可以看下面的實戰(zhàn)中代碼。
8、考慮問題五,組件之間資源命名
組件多了,資源命名難免會出現(xiàn)重復(fù),比如類名,比如layout名字,比如string名字,再比如其他的資源名等等,在實際的開發(fā)中,一旦名字重復(fù),有可能造成資源沖突等問題,為了避免這樣的問題出現(xiàn),一般我們在組件化開發(fā)的時候,以組件的名字做為前綴,可以進行避免。
團隊協(xié)作開發(fā),我們可以進行資源約束,如果不采取約束,就報紅給開發(fā)者進行相關(guān)提示。
android {
....
resourcePrefix "module_xxx"
....
}
復(fù)制代碼
三、組件化實戰(zhàn)
經(jīng)過第2項中的闡述,相信對組件化已經(jīng)有了一個初步的認識,那么接下來我們就一步一步進入實戰(zhàn)當(dāng)中,需要注意的是,在實際的業(yè)務(wù)中,基礎(chǔ)庫肯定是先有的,畢竟是我們開發(fā)項目的能力項,因為做為一個Demo,不可能在進行對基礎(chǔ)庫做一個封裝,那樣就太耗時了,所以啊,老鐵們,我們直接進行業(yè)務(wù)層的書寫,畢竟,組件化,一般就是針對于業(yè)務(wù)層而言。
1、創(chuàng)建項目
我這里首先創(chuàng)建一個項目,畢竟不是實際業(yè)務(wù),目前就先創(chuàng)建四個模塊,app是主模塊,common是中間層,account和code是組件,也就是實際的業(yè)務(wù)模塊,如下圖。
[圖片上傳失敗...(image-86dbd4-1663164225794)]
具體的組件架構(gòu)流程圖如下,common做為中間層,角色承擔(dān)十分重要,比如公用的方法,屬性,數(shù)據(jù),資源等,都可以放置這層,便于業(yè)務(wù)模塊直接的中轉(zhuǎn)跳轉(zhuǎn)或數(shù)據(jù)獲取。
[圖片上傳失敗...(image-702930-1663164225794)]
2、統(tǒng)一依賴,統(tǒng)一版本號
根項目下新建一個gradle文件,如下圖
[圖片上傳失敗...(image-f66f46-1663164225794)]
gradle文件主要是做為組件之間的統(tǒng)一使用,比如版本號,比如依賴等等,有了這樣的一個公共配置,所有的組件都可以進行統(tǒng)一管理,當(dāng)然,除了部分無法實現(xiàn)之外。
全部代碼如下,相關(guān)注釋很是齊全。
project.ext {
//是否允許module單獨調(diào)試
isModuleDebug = false
moduleName = ""http://單獨調(diào)試module名
//基礎(chǔ)信息配置
compileSdkVersion = 30
buildToolsVersion = "30.0.2"
minSdkVersion = 21
targetSdkVersion = 30
applicationId = "com.abner.assembly"
versionCode = 1
versionName = "1.0.0"
//設(shè)置app配置
setAppDefaultConfig = {
extension ->
//指定為application
extension.apply plugin: 'com.android.application'
extension.description "app"
//公共的apply 主要是用于三方庫
extension.apply plugin: 'kotlin-android'
extension.apply plugin: 'kotlin-parcelize'
extension.apply plugin: 'kotlin-kapt'
appImplementation = "app"
//設(shè)置項目的android
setAppAndroidConfig extension.android
//設(shè)置項目的三方庫依賴
setDependencies extension.dependencies
}
//設(shè)置application 公共的android配置
setAppAndroidConfig = {
extension ->
extension.compileSdkVersion project.ext.compileSdkVersion
extension.buildToolsVersion project.ext.buildToolsVersion
extension.defaultConfig {
applicationId project.ext.applicationId
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
versionCode project.ext.versionCode
versionName project.ext.versionName
extension.flavorDimensions "versionCode"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
extension.compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
extension.kotlinOptions {
jvmTarget = '1.8'
}
extension.buildFeatures.dataBinding = true
}
//動態(tài)改變,用于單模塊調(diào)試
setAppOrLibDefaultConfig = {
extension ->
if (project.ext.isModuleDebug && project.ext.moduleName == project.name) {
extension.apply plugin: 'com.android.application'
extension.description "app"
} else {
extension.apply plugin: 'com.android.library'
extension.description "lib"
}
extension.apply plugin: 'kotlin-android'
extension.apply plugin: 'kotlin-parcelize'
extension.apply plugin: 'kotlin-kapt'
appImplementation = project.name
//設(shè)置通用Android配置
setAppOrLibAndroidConfig extension.android
//設(shè)置通用依賴配置
setDependencies extension.dependencies
}
//設(shè)置通用的 android配置(可作為project單獨調(diào)試)
setAppOrLibAndroidConfig = {
extension ->
extension.compileSdkVersion project.ext.compileSdkVersion
extension.buildToolsVersion project.ext.buildToolsVersion
extension.defaultConfig {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
versionCode project.ext.versionCode
versionName project.ext.versionName
extension.flavorDimensions "versionCode"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
//ARouter 編譯生成路由
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
//使用的jdk版本
extension.compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
extension.kotlinOptions {
jvmTarget = '1.8'
}
//動態(tài)改變清單文件資源指向
extension.sourceSets {
main {
if (project.ext.isModuleDebug && project.ext.moduleName == project.name) {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
//公用的三方庫依賴,慎重引入,主要引入基礎(chǔ)庫依賴
setDependencies = {
extension ->
extension.implementation fileTree(dir: 'libs', include: ['*.jar'])
extension.implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
extension.implementation 'androidx.core:core-ktx:1.3.1'
extension.implementation 'androidx.appcompat:appcompat:1.3.1'
extension.implementation 'com.google.android.material:material:1.4.0'
extension.implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
extension.testImplementation 'junit:junit:4.+'
extension.androidTestImplementation 'androidx.test.ext:junit:1.1.2'
extension.androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
extension.kapt 'com.alibaba:arouter-compiler:1.5.2'
if(appImplementation!="common"){
//common做為中間層,所有的Module都要依賴
extension.implementation extension.project(path: ':common')
}
//針對每個Module單獨進行依賴
switch (appImplementation) {
case "app":
extension.implementation extension.project(path: ':account')
extension.implementation extension.project(path: ':code')
break
case "account":
break
case "common"://common組件是一個中間層,所有的組件都需要依賴此組件,公共的依賴便可放到這里
extension.api 'com.alibaba:arouter-api:1.5.2'//ARouter依賴
break
}
}
}
復(fù)制代碼
相關(guān)注釋都有,相信大家一看便懂,無非就是抽取了公共的部分,相當(dāng)于一個模板,大家完全可以拿走直接用,只需要改成自己的相關(guān)依賴即可,不過,針對這個文件,還是要做一個簡單的概述。
概述一,如何單組件運行
只需要改動下面的兩個參數(shù)即可,isModuleDebug參數(shù),true就是開啟單組件運行模式,moduleName就是你要運行的那個Module名字,之所以定義兩個參數(shù),是為了精準(zhǔn)到位,組件之間清晰,方便部分組件之間依賴。
//是否允許module單獨調(diào)試
isModuleDebug = false
moduleName = ""http://單獨調(diào)試module名
復(fù)制代碼
概述二,統(tǒng)一管理,以后只維護這個文件即可
有了這個文件之后,其他的Module中的build.gradle里就可以根據(jù)需要進行簡寫,比如,app下:
[圖片上傳失敗...(image-a604a6-1663164225793)]
比如其他組件下
[圖片上傳失敗...(image-5b62fb-1663164225793)]
這樣,不管是依賴,還是版本號,以后統(tǒng)一的在一個文件里進行增加和改變,杜絕私自添加三方依賴,便于審閱。
概述三,單個組件如何進行依賴
通過appImplementation參數(shù)進行識別是哪一個Module,然后進行單獨區(qū)分即可。
[圖片上傳失敗...(image-9ac87c-1663164225793)]
概述四,動態(tài)改變清單文件資源指向
文件中已經(jīng)邏輯處理
[圖片上傳失敗...(image-28318f-1663164225793)]
3、單Module運行
在第2步中,對所有的組件進行了依賴和版本號管理,同樣也加入了動態(tài)改變清單文件資源指向,那么一個單獨的組件如何運行呢?我們以Demo中的account模塊舉例。
第1步,按照動態(tài)改變清單文件資源指向相關(guān)邏輯,新建mainfest目錄,記住,必須和你定義的路徑保持一致,如下:
[圖片上傳失敗...(image-9ae835-1663164225793)]
第2步,在manifest文件下復(fù)制一份AndroidManifest.xml文件。
[圖片上傳失敗...(image-62492d-1663164225793)]
需要注意,manifest文件下的AndroidManifest.xml文件和組件下的AndroidManifest.xml文件是有區(qū)別的,區(qū)別就是,當(dāng)你選擇組件運行時,會編譯manifest文件下的,當(dāng)做依賴使用時,走組件下的,除此之外,當(dāng)你選擇組件運行時,manifest文件下的AndroidManifest.xml文件必須有主入口,如下:
[圖片上傳失敗...(image-626521-1663164225793)]
而組件下清單文件,是不需要這個主入口的。
AndroidManifest.xml文件創(chuàng)建完之后,如果需要有初始化配置的,大家可以自行創(chuàng)建Application,待相關(guān)組件名,樣式引入后,更改統(tǒng)一管理gradle文件下的參數(shù),如下:
[圖片上傳失敗...(image-b5d8f9-1663164225793)]
再看模塊目錄,account已經(jīng)和app保持一致,可以單獨運行了。
[圖片上傳失敗...(image-5acd32-1663164225793)]
4、組件之間進行跳轉(zhuǎn)
新建的項目中,我把account和code作為業(yè)務(wù)組件,兩個組件是互不關(guān)聯(lián)的,我們要實現(xiàn)的就是這兩個組件之間的跳轉(zhuǎn),組件之間跳轉(zhuǎn)使用的是阿里的ARouter,大家可以按照官網(wǎng)進行一步步依賴,我在第2步中已經(jīng)依賴進去,最新的版本,如下所示:
[圖片上傳失敗...(image-b6d81-1663164225793)]
依賴之后,按照官網(wǎng)對其初始化
[圖片上傳失敗...(image-549aa5-1663164225793)]
在common中間層組件之中,我創(chuàng)建了兩個文件,一個路由常量類,一個是路由工具類,大家直接可以看源碼,這里就不貼代碼了,就是對ARouter做了一個簡單的封裝而已。
頁面跳轉(zhuǎn)
從Account組件跳轉(zhuǎn)到code組件。
給目標(biāo)頁面設(shè)置路由地址
@Route(path = CommonRouterConstant.CODE)
class CodeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_code)
}
}
復(fù)制代碼
跳轉(zhuǎn)目標(biāo)路由
CommonRouterManger.get().navigationActivity(CommonRouterConstant.CODE)
復(fù)制代碼
頁面帶參數(shù)跳轉(zhuǎn)
已經(jīng)做了一個簡單的封裝,可通過下面的方式,可變參數(shù),值是Any類型,大家可以參考源碼。
CommonRouterManger.get().navigationActivityParams(
CommonRouterConstant.CODE,
"params" to "參數(shù)",
"params2" to 100
)
復(fù)制代碼
接收參數(shù)呢,有兩種形式,一種是intent來接收,一種是Router形式。
intent接收參數(shù)
val stringExtra = intent.getStringExtra("params")
val intExtra = intent.getIntExtra("params2", -1)
復(fù)制代碼
ARouter形式接收參數(shù)
@Autowired(name = "params")
@JvmField
var mParams = ""
@Autowired(name = "params2")
@JvmField
var mParams2 = -1
復(fù)制代碼
ARouter形式接收參數(shù),請一定要記得初始化,一般在父類中即可。
ARouter.getInstance().inject(this)
復(fù)制代碼
5、組件數(shù)據(jù)共享
數(shù)據(jù)共享的場景比較常見,比如用戶信息組件,當(dāng)用戶登錄后,需要保存用戶的數(shù)據(jù),那么其他相關(guān)組件怎么獲取使用呢,這種時候,我們就可以讓中間組件common,擔(dān)負起應(yīng)有的責(zé)任,因為common是所有業(yè)務(wù)組件都依賴的,用戶信息組件存儲數(shù)據(jù)后,在common組件里寫一個工具類即可,這樣,其他組件都可以通過common組件來獲取數(shù)據(jù)了。
這種方式比較簡單,大家可以看下源碼,源碼中簡單舉了一個例子,在common組件里定義了工具類,數(shù)據(jù)模型,提供了存儲和獲取數(shù)據(jù)的方法,然后account組件里模擬登錄信息存儲,在code組件里進行模擬獲取。
當(dāng)然了除了采用這種方式之外,也可以使用服務(wù)的方式進行獲取,首先在common組件定義接口,定義其中自己想要獲取數(shù)據(jù)的方法,如下:
interface AccountUserService : IProvider {
/**
* AUTHOR:AbnerMing
* INTRODUCE:獲取用戶信息
*/
fun getUser(): UserInfo?
}
復(fù)制代碼
定義好接口之后,同樣也分為兩步,一步是在需要設(shè)置數(shù)據(jù)的組件里進行傳遞數(shù)據(jù),一步是在需要數(shù)據(jù)的組件里獲取數(shù)據(jù),也就是Demo中的account組件實現(xiàn)其接口,進行傳遞數(shù)據(jù),如下:
@Route(path = CommonRouterConstant.USER_INFO, name = "AccountUserServiceIml")
class AccountUserServiceIml : AccountUserService {
override fun getUser(): UserInfo? {
//獲取用戶信息后,轉(zhuǎn)成需要的對象
val userJson = DataStoreUtils.getSyncData("user", "")
if (TextUtils.isEmpty(userJson)) {
return null
}
return Gson().fromJson(userJson, UserInfo::class.java)
}
}
復(fù)制代碼
哪個組件需要用到數(shù)據(jù),就可以如下操作:
val iml = CommonRouteServiceManager.provide(AccountUserService::class.java)
//模擬,通過服務(wù)進行獲取用戶相關(guān)信息
iml?.getUser()?.let {
Toast.makeText(this, it.data?.nickName, Toast.LENGTH_SHORT).show()
}
復(fù)制代碼
CommonRouteServiceManager是對ARouter獲取服務(wù)做了一個簡單的封裝,大家可以看下源碼。
6、組件之間調(diào)用功能
功能的調(diào)用其實還是用到了ARouter的一個獲取服務(wù)的功能,和獲取數(shù)據(jù)的思路基本一致,這里舉一個簡單的例子,在Account里有一個彈出Dialog的功能,但是呢,是在code組件里進行調(diào)用,就可以如下操作:
在原有的Service里增加方法:
interface AccountUserService : IProvider {
/**
* AUTHOR:AbnerMing
* INTRODUCE:測試彈出Dialog
*/
fun showDialog()
}
復(fù)制代碼
在account組件里進行實現(xiàn)方法
@Route(path = CommonRouterConstant.USER_INFO, name = "AccountUserServiceIml")
class AccountUserServiceIml : AccountUserService {
/**
* AUTHOR:AbnerMing
* INTRODUCE:測試彈窗或其他功能
*/
override fun showDialog() {
AlertDialog.Builder(mContext)
.setTitle("測試一個Dialog彈出框")
.setMessage("簡單測試以下")
.setNegativeButton("取消") { _, _ ->
}
.setPositiveButton("確定") { _, _ ->
}
.show()
}
private var mContext: Context? = null
override fun init(context: Context?) {
mContext = context
}
}
復(fù)制代碼
在code組件里進行調(diào)用
val iml = CommonRouteServiceManager.provide(AccountUserService::class.java)
iml?.init(this)
iml?.showDialog()
復(fù)制代碼
四、開源及Demo
https://github.com/AbnerMing888/AndroidAssembly
目前呢,源碼中只是測試的Demo,簡單的舉例了組件化開發(fā)的相關(guān)流程及所需要,所注意的事項,大家可以做為參考,而實際的開發(fā)中,除了業(yè)務(wù)的不同,但所執(zhí)行的流程基本是一致的,還有,關(guān)于ARouter的使用,其實官網(wǎng)中還有很多的使用方式,這個大家去官網(wǎng)看即可,至于基礎(chǔ)庫的封裝,每個公司所實現(xiàn)的方式不同,最重要的是業(yè)務(wù)層的組件化實現(xiàn)。
作者:二流小碼農(nóng)
鏈接:https://juejin.cn/post/7142297951023415332
來源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。