Android組件化開(kāi)發(fā)學(xué)習(xí)

轉(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)。

官方文檔如下

github.com/alibaba/ARo…

”一個(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)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容