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