Android組件化實(shí)踐

隨著開(kāi)發(fā)人員不斷增多,如果沒(méi)有使用合理的開(kāi)發(fā)架構(gòu),規(guī)范化一定的代碼的寫(xiě)法黎烈,隨著時(shí)間推移會(huì)使得代碼越來(lái)越臃腫,維護(hù)成本越來(lái)越高匀谣,離職入職的人員都難以交接照棋。組件化現(xiàn)已是一個(gè)成熟方案,是一個(gè)團(tuán)隊(duì)多人開(kāi)發(fā)的首選方案武翎,我們?cè)诮M件化重構(gòu)的過(guò)程烈炭,是一個(gè)規(guī)范化各種代碼結(jié)構(gòu)的過(guò)程,也是對(duì)一個(gè)現(xiàn)有業(yè)務(wù)邏輯梳理的過(guò)程宝恶,去除年久失修的代碼符隙,優(yōu)化以前做的不合理的地方。

組件的劃分

  • 功能組件

lib_xxxx命名垫毙,如網(wǎng)絡(luò)請(qǐng)求組件(lib_http)霹疫、圖片選擇器組件(lib_pictureselector)...這些所有模塊都有可能用到并且實(shí)現(xiàn)單一功能的組件稱為功能組件

  • 業(yè)務(wù)組件

module_xxxx開(kāi)頭,根據(jù)業(yè)務(wù)自行拆分综芥,類似登錄組件(module_login)丽蝎、用戶個(gè)人信息組件(module_user)、首頁(yè)組件(module_main)等

框架結(jié)構(gòu)圖

框架結(jié)構(gòu)圖.png

Demo示例圖

image.png
  • app:

app空殼工程膀藐,依賴所有的業(yè)務(wù)組件屠阻,不存放任何邏輯代碼。

  • lib_common

基礎(chǔ)公共模塊额各,最底層的庫(kù)国觉,依賴了各種功能組件(lib_xxxx)、第三方SDK(友盟虾啦,bugly等)麻诀,二次封裝各種工具類和開(kāi)源庫(kù)蚜枢,存放各種自定義view(如圓角imageview)等跟業(yè)務(wù)無(wú)任何關(guān)系的東西。

  • lib_base

業(yè)務(wù)公共模塊针饥,存放各個(gè)業(yè)務(wù)組件的公共部分厂抽,如BaseActivity,BaseApplication丁眼,基礎(chǔ)域名筷凤,各種全局常量等,所有業(yè)務(wù)模塊只需要依賴此模塊即可苞七,無(wú)需依賴其他模塊藐守。

  • lib_http

拷貝 https://github.com/zhou-you/RxEasyHttp 開(kāi)源框架源碼,具有支持接口數(shù)據(jù)緩存蹂风,支持多種緩存策略卢厂,支持多域名等功能,能有效幫我們統(tǒng)一整個(gè)APP的請(qǐng)求方式惠啄,每個(gè)模塊只需要關(guān)注自己的請(qǐng)求接口是什么慎恒,防止單純使用Retrofit帶來(lái)的寫(xiě)法過(guò)亂的問(wèn)題,不一定要用這個(gè)框架撵渡,只是想表達(dá)APP內(nèi)需要一個(gè)高定制化的網(wǎng)絡(luò)框架融柬,如果項(xiàng)目使用MVVM架構(gòu)那么可以用Retrofit和Kotlin協(xié)程參考優(yōu)秀的框架自己封一個(gè)。

  • lib_pictureselector

第三方開(kāi)源圖片選擇器

  • lib_xxxx.....

模塊單獨(dú)編譯

每個(gè)功能模塊單獨(dú)編譯成一個(gè)獨(dú)立APP是組件化開(kāi)發(fā)中最突出的一個(gè)優(yōu)點(diǎn)趋距,能幫我們減少大量的編譯時(shí)間粒氧,我們只需要關(guān)注自己模塊所正在開(kāi)發(fā)的功能,一切配置結(jié)束后节腐,只需要在config.gradle中打開(kāi)開(kāi)關(guān)即可


image.png

這里有一個(gè)注意的點(diǎn)外盯,單獨(dú)編譯的時(shí)候像用戶數(shù)據(jù),是否登錄等數(shù)據(jù)就獲取不到了翼雀,因?yàn)槲覀冎笆窃谄渌K獲取到的這些數(shù)據(jù)饱苟,現(xiàn)在相當(dāng)于一個(gè)沒(méi)有那些東西的一個(gè)全新APP,需要我們?cè)谛枰獑为?dú)編譯的那個(gè)模塊下的application直接寫(xiě)死一些數(shù)據(jù)锅纺,如用戶數(shù)據(jù)等(前提是有用到的話)掷空,像請(qǐng)求接口需要一些基礎(chǔ)參數(shù)(auth,token...)囤锉,那么這一部分基礎(chǔ)參數(shù)也要先寫(xiě)死先坦弟。

組件初始化

每一個(gè)業(yè)務(wù)模塊只需要依賴lib_common底層庫(kù)即可,像有的模塊需要引入其他的第三方庫(kù)官地,那么只在該模塊的gradle中去引入酿傍,但是這個(gè)時(shí)候可能會(huì)有問(wèn)題,有一些庫(kù)的初始化是需要在application的驱入,而base層由于沒(méi)引入這個(gè)庫(kù)是沒(méi)辦法初始化的赤炒,關(guān)于這個(gè)問(wèn)題可以參考(https://juejin.cn/post/6866628586414997512#heading-5)中組件初始化的那個(gè)部分氯析,不過(guò)一般情況我們只需要base層的BaseApplication把一些常用的一起初始化了就好,如下:

image.png

這里對(duì)有一些第三方SDK進(jìn)行了延時(shí)初始化莺褒,一方面是因?yàn)殡[私政策的問(wèn)題掩缓,一方面是可以提高啟動(dòng)速度,這里我用了新建一個(gè)IntentService的形式:
image.png

資源命名沖突

由于在最后打包成APK的時(shí)候會(huì)進(jìn)行資源的合并遵岩,如果資源文件相同最后會(huì)被合成一份你辣,為避免這種情況,需要在每個(gè)模塊下的gradle中配置resourcePrefix字段尘执,每個(gè)模塊的資源文件以其模塊名開(kāi)頭舍哄,如果不按照這個(gè)規(guī)則AndroidStudio會(huì)有警告。


image.png

image.png

配置文件統(tǒng)一抽取

為方便所有配置信息管理誊锭,我們將所有依賴的第三方庫(kù)表悬、包名、targetSdkVersion丧靡、compileSdkVersion等統(tǒng)一抽取到config.gradle文件中

image.png

我們知道 lib_common 依賴此config配置文件下的所有第三方SDK蟆沫,開(kāi)源庫(kù)等,如果一個(gè)個(gè)寫(xiě)會(huì)很麻煩窘行,這里我們直接寫(xiě)一個(gè)循環(huán)饥追,依次依賴config下的dependencies數(shù)組即可图仓,如下:
image.png

每個(gè)業(yè)務(wù)模塊下仍然存在很多重復(fù)的配置信息罐盔,諸如compileSdkVersion、buildToolsVersion救崔、minSdkVersion等惶看,為了好維護(hù),讓代碼優(yōu)雅點(diǎn)六孵,抽取出這些共性的東西命名為module-basic.gradle纬黎,業(yè)務(wù)模塊引用即可,如下:
image.png

image.png

模塊間的通信

  • 模塊間的跳轉(zhuǎn)

模塊間沒(méi)有任何聯(lián)系劫窒,所以如果需要跳轉(zhuǎn)這里用的是阿里的ARouter(https://github.com/alibaba/ARouter)本今,可以在base層做下簡(jiǎn)單的封裝,具體使用可參考github主巍,封裝后調(diào)用如下:

image.png

同時(shí)ARouter支持獲取fragment實(shí)例冠息,像一般APP首頁(yè)結(jié)構(gòu)是一個(gè)Activity加上四五個(gè)fragment,可以將fragment寫(xiě)在對(duì)應(yīng)的模塊孕索,在main模塊使用如下調(diào)用方式即可獲取fragment實(shí)例
image.png

  • 模塊間的數(shù)據(jù)通信

之前我的做法是直接把一些共性的東西放在base層逛艰,像LoginUtil判斷用戶登錄狀態(tài)這種東西,這變相把實(shí)現(xiàn)邏輯都寫(xiě)在了base層搞旭,不符合組件化思想散怖,正確的思想應(yīng)該是每個(gè)模塊實(shí)現(xiàn)每個(gè)模塊的邏輯菇绵,像LoginUtil的實(shí)現(xiàn)邏輯就應(yīng)該是login模塊去做,然后其他模塊只需要知道怎么調(diào)用就好镇眷,具體的做法是需要新起一個(gè)module(如:export_login)去存放login所需要暴露的服務(wù)(interface)咬最,然后interface的具體實(shí)現(xiàn)在module_login去做,哪個(gè)模塊需要用到就去依賴這個(gè)暴露服務(wù)組件欠动,這里面同樣是借助了ARouter丹诀,這里只做簡(jiǎn)單闡述,具體做法可參考該文章(https://juejin.cn/post/6881116198889586701#heading-21)組件間的通信那部分翁垂。

各組件分包應(yīng)保持一致

各個(gè)業(yè)務(wù)組件module應(yīng)該保持一致的分包結(jié)構(gòu)铆遭,這樣有時(shí)候我們需要去維護(hù)別人開(kāi)發(fā)的模塊的時(shí)候,上手才方便沿猜,同時(shí)也有利于項(xiàng)目保持一致結(jié)構(gòu)枚荣,下面分包只是示例,不一定要按照這個(gè)分啼肩,只是想強(qiáng)調(diào)需要保持一致橄妆。


image.png

為什么要把網(wǎng)絡(luò)請(qǐng)求拆分成一個(gè)獨(dú)立的功能庫(kù)(lib_http)

網(wǎng)絡(luò)請(qǐng)求是一個(gè)APP中非常重要的一塊,沒(méi)改版之前我是用單例簡(jiǎn)單封裝的Retrofit祈坠,帶來(lái)的問(wèn)題是:

  • ApiService過(guò)于臃腫害碾,成千上百個(gè)接口寫(xiě)在了一起
  • 如果App存在多域名的情況下,需要新建多個(gè)RetrofitClient赦拘,看起來(lái)也不優(yōu)雅
  • 緩存問(wèn)題不好做慌随,如果某個(gè)Post方式的接口需要緩存,寫(xiě)法很挫躺同,要么手動(dòng)判斷下有沒(méi)有緩存數(shù)據(jù)阁猜,要么結(jié)合Rxjava在請(qǐng)求代碼層做些合并操作,更沒(méi)有什么緩存策略可言
  • 有一些請(qǐng)求是外部鏈接的話又得去專門(mén)寫(xiě)一個(gè)不是返回BaseBean<T>形式蹋艺,純返回json的
  • ......
    基于這種種情況剃袍,寫(xiě)法會(huì)有五花八門(mén),如果人員多起來(lái)捎谨,如果網(wǎng)絡(luò)請(qǐng)求庫(kù)功能不夠完善民效,每個(gè)人都去寫(xiě)一套,那就會(huì)很亂涛救,我預(yù)想的結(jié)果是:
  • 每個(gè)業(yè)務(wù)組件無(wú)論什么情況下調(diào)用網(wǎng)絡(luò)請(qǐng)求的代碼應(yīng)該保持一致
  • 業(yè)務(wù)組件的 API 由自己模塊去管理畏邢,其他模塊并不需要知道
  • 支持各種緩存策略,支持多域名州叠,多種請(qǐng)求方式等功能
  • ......
    底層需要一個(gè)如RxEasyHttp(https://github.com/zhou-you/RxEasyHttp)棵红,OkGo(https://github.com/jeasonlzy/okhttp-OkGo)這樣優(yōu)秀的網(wǎng)絡(luò)請(qǐng)求庫(kù)封裝,如果使用MVVM+Kotlin咧栗,我們可以參考使用Kotlin+協(xié)程自己封一個(gè)逆甜。
之前組件化項(xiàng)目的使用示例如下:

每個(gè)模塊只需要關(guān)心自己請(qǐng)求的是哪個(gè)接口虱肄,返回什么數(shù)據(jù),怎么使用即可交煞。


image.png

無(wú)論什么情況咏窿,外層都是這樣的一種調(diào)用形式(支持獨(dú)立接口的各種配置,如緩存策略啥的...)素征,保持統(tǒng)一有利于我們以后不想用Retrofit想用其他庫(kù)的時(shí)候集嵌,只需要修改lib_http即可,業(yè)務(wù)組件邏輯代碼不用修改御毅。


image.png

基于Glide的圖片二次封裝

為什么要封裝根欧?一是因?yàn)橹苯邮褂肎lide寫(xiě)法較長(zhǎng),二是可以讓各個(gè)業(yè)務(wù)模塊的調(diào)用方式統(tǒng)一端蛆,目的同樣同上面一樣凤粗,如果封裝的好,萬(wàn)一以后不想用Glide今豆,想換其他的圖片加載庫(kù)嫌拣,我們的業(yè)務(wù)邏輯才不需要改動(dòng)到,我下面的寫(xiě)法是直接封裝在了base層呆躲,如果我們想拆分的粒度比較小异逐,可以單獨(dú)在抽出一個(gè)lib_imageloader的功能組件來(lái)寫(xiě):


image.png

調(diào)用方式如下:


image.png

如果有多個(gè)業(yè)務(wù)模塊共用某個(gè)接口怎么辦?

我現(xiàn)在的做法是抽取這個(gè)接口到base層作為公共Url插掂,如果是超過(guò)四五個(gè)模塊多用到的灰瞻,就新建一個(gè)IntentService去請(qǐng)求,然后用Eventbus吧請(qǐng)求結(jié)果暴露出去燥筷。


image.png

屏幕適配方案

屏幕適配采用字節(jié)跳動(dòng)修改DisplayMetrics的方案:https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA箩祥,很多大神基于此方案開(kāi)源了一些庫(kù),像AutoSize肆氓,但是仍然存在有時(shí)候因?yàn)檫m配失效or橫豎屏切換導(dǎo)致樣式全亂掉的問(wèn)題,特別是一些系統(tǒng)彈窗底瓣,橫豎屏需要展示不同彈窗的時(shí)候谢揪,更容易體現(xiàn)出這個(gè)問(wèn)題,這個(gè)問(wèn)題本質(zhì)都是用的dp適配的原因捐凭,這里可以使用blankj大神的用pt單位適配可以減少適配失效問(wèn)題拨扶,具體可參考 https://blankj.com/2018/12/18/android-adapt-screen-killer/,該方案缺點(diǎn)是如果重構(gòu)的話改動(dòng)成本較大茁肠,因?yàn)椴季种械膁p患民、sp需要都換成pt。

常用的第三方開(kāi)源庫(kù)

好用的第三方庫(kù)可以大幅度提升我們的開(kāi)發(fā)效率垦梆,同時(shí)在多人開(kāi)發(fā)中更有利于我們保持一致的代碼樣式匹颤,想象一下一個(gè)DeviceUtils獲取唯一的設(shè)備ID的方法仅孩,同事A寫(xiě)一個(gè)方法,同時(shí)B不知道A寫(xiě)過(guò)了印蓖,他又寫(xiě)了一個(gè)辽慕,同事C又新建了一個(gè)PhoneUtils寫(xiě)一個(gè),那代碼會(huì)越來(lái)越冗余赦肃,越來(lái)越亂:

優(yōu)秀的utils庫(kù)溅蛉,如果我們需要某一個(gè)XXUtils工具庫(kù)的時(shí)候,我們應(yīng)該保持一致意識(shí)他宛,優(yōu)先在該庫(kù)里面尋找方法船侧,沒(méi)有的話再補(bǔ)充到base層并周知其他同事

recycleview adapter如果用原生的方式,像多item布局厅各,item點(diǎn)擊事件啥的同樣是寫(xiě)法很多勺爱,使用brvah有效幫我們統(tǒng)一各種寫(xiě)法,方便我們閱讀其他同事的代碼讯检。

  • ......
    這些庫(kù)都是幾萬(wàn)star很優(yōu)秀的庫(kù)琐鲁,但是不一定就一定要用,如果我們自認(rèn)為封的比別人好人灼,也可以自己寫(xiě)一套围段,主要是我們要防止一些模板代碼的重復(fù),然后每個(gè)人重復(fù)的樣子還不一樣.....

MVVM+Kotlin

MVVM+Kotlin+Jetpack現(xiàn)也已是主流投放,我們應(yīng)該多使用ViewBinding等來(lái)替代ButterKnife等奈泪,多使用Lifecycle等具有生命感知能力的組件來(lái)避免內(nèi)存泄露,多使用Livedata灸芳、DataBinding等組件來(lái)數(shù)據(jù)驅(qū)動(dòng)UI涝桅,合理的根據(jù)業(yè)務(wù)+MVVM思想合理的拆分邏輯到ViewModel,Repository烙样,UI層冯遂,防止一個(gè)Activity幾千行代碼的情況,這會(huì)使維護(hù)成本大大增加谒获,官方的MVVM架構(gòu)圖如下:


image.png

參考文章

關(guān)于組件化更詳細(xì)的介紹參考下面這幾篇:
https://juejin.cn/post/6844903649102004231#heading-18
https://juejin.cn/post/6866628586414997512#heading-6
https://juejin.cn/post/6881116198889586701#heading-21

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末度秘,一起剝皮案震驚了整個(gè)濱河市啸如,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖酪刀,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棒拂,死亡現(xiàn)場(chǎng)離奇詭異滨达,居然都是意外死亡巷燥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)权悟,“玉大人砸王,你說(shuō)我怎么就攤上這事〗┣郏” “怎么了处硬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拇派。 經(jīng)常有香客問(wèn)我荷辕,道長(zhǎng),這世上最難降的妖魔是什么件豌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任疮方,我火速辦了婚禮,結(jié)果婚禮上茧彤,老公的妹妹穿的比我還像新娘骡显。我一直安慰自己,他們只是感情好曾掂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布惫谤。 她就那樣靜靜地躺著,像睡著了一般珠洗。 火紅的嫁衣襯著肌膚如雪溜歪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天许蓖,我揣著相機(jī)與錄音蝴猪,去河邊找鬼。 笑死膊爪,一個(gè)胖子當(dāng)著我的面吹牛自阱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播米酬,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沛豌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了淮逻?” 一聲冷哼從身側(cè)響起琼懊,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爬早,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體启妹,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筛严,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饶米。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨啃。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡车胡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出照瘾,到底是詐尸還是另有隱情匈棘,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布析命,位于F島的核電站主卫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹃愤。R本人自食惡果不足惜簇搅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望软吐。 院中可真熱鬧瘩将,春花似錦、人聲如沸凹耙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肖抱。三九已至备典,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虐沥,已是汗流浹背熊经。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欲险,地道東北人镐依。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像天试,于是被迫代替她去往敵國(guó)和親槐壳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 前言 相信各位小伙伴們對(duì)組件化開(kāi)發(fā)都不陌生了,本文只對(duì)我所理解和使用的組件化開(kāi)發(fā)方案做一個(gè)總結(jié),有不正確或者需要改...
    小豪丶ace閱讀 587評(píng)論 0 2
  • 引子 一般情況下喜每,一個(gè) Android 項(xiàng)目务唐,隨著業(yè)務(wù)的發(fā)展和擴(kuò)張,會(huì)有許多 Activity带兜、Fragment ...
    修之竹閱讀 1,371評(píng)論 2 4
  • 0 背景 早前嚴(yán)選 Android 工程枫笛,業(yè)務(wù)模塊和功能模塊不多,工程較為簡(jiǎn)單刚照,全部的業(yè)務(wù)代碼均在主 app 工程...
    zyl06閱讀 6,194評(píng)論 10 54
  • 概述 概念 馬甲包:和主產(chǎn)品包擁有同樣的內(nèi)容和功能刑巧,除了icon和應(yīng)用名稱不能完全一致,其他基本一致。 主包:主A...
    孔睿閱讀 474評(píng)論 2 0
  • 背景 什么是組件化啊楚?組件化就是模塊化吠冤,在Android工程實(shí)踐中可以實(shí)現(xiàn)單獨(dú)編譯、運(yùn)行恭理、調(diào)試拯辙。 --個(gè)人見(jiàn)解 為什...
    WangDDY閱讀 2,202評(píng)論 3 3