Android組件化開(kāi)發(fā)實(shí)踐和案例分享

目錄介紹

  • 1.為什么要組件化
    • 1.1 為什么要組件化
    • 1.2 現(xiàn)階段遇到的問(wèn)題
  • 2.組件化的概念
    • 2.1 什么是組件化
    • 2.2 區(qū)分模塊化與組件化
    • 2.3 組件化優(yōu)勢(shì)好處
    • 2.4 區(qū)分組件化和插件化
    • 2.5 application和library
  • 3.創(chuàng)建組件化框架
    • 3.1 傳統(tǒng)APP架構(gòu)圖
    • 3.2 組件化需要考慮問(wèn)題
    • 3.3 架構(gòu)設(shè)計(jì)圖
    • 3.4 組件通信是通過(guò)路由轉(zhuǎn)發(fā)
    • 3.5 解決考慮問(wèn)題
    • 3.6 業(yè)務(wù)組件的生命周期
    • 3.7 Fragment通信難點(diǎn)
  • 4.實(shí)際開(kāi)發(fā)案例
    • 4.1 組件化實(shí)踐的開(kāi)源項(xiàng)目
    • 4.1 如何創(chuàng)建模塊
    • 4.2 如何建立依賴(lài)
    • 4.3 如何統(tǒng)一配置文件
    • 4.4 組件化的基礎(chǔ)庫(kù)
    • 4.5 組件模式和集成模式如何切換
    • 4.6 組件化解決重復(fù)依賴(lài)
    • 4.7 組件化注意要點(diǎn)
    • 4.8 組件化時(shí)資源名沖突
    • 4.9 組件化開(kāi)發(fā)遇到問(wèn)題
  • 5.組件間通信
    • 5.1 選擇那個(gè)開(kāi)源路由庫(kù)
    • 5.2 阿里Arouter基礎(chǔ)原理
    • 5.3 使用Arouter注意事項(xiàng)
    • 5.4 如何獲取fragment實(shí)例
  • 6.關(guān)于其他
    • 6.1 參考博客鏈接
    • 6.2 我的博客介紹
    • 6.3 開(kāi)源項(xiàng)目地址

1.為什么要組件化

1.1 為什么要組件化

  • APP迭代維護(hù)成本增高
    • 投資界棱貌,新芽喻粹,項(xiàng)目工廠(chǎng)等APP自身在飛速發(fā)展同蜻,版本不斷迭代净神,新功能不斷增加,業(yè)務(wù)模塊數(shù)量不斷增加扼仲,業(yè)務(wù)上的處理邏輯越變?cè)綇?fù)雜嫉入,同時(shí)每個(gè)模塊代碼也變得越來(lái)越多朴乖,這就引發(fā)一個(gè)問(wèn)題,所維護(hù)的代碼成本越來(lái)越高窜护,稍微一改動(dòng)可能就牽一發(fā)而動(dòng)全身效斑,改個(gè)小的功能點(diǎn)就需要回歸整個(gè)APP測(cè)試,這就對(duì)開(kāi)發(fā)和維護(hù)帶來(lái)很大的挑戰(zhàn)柱徙。
  • 多人組合需要組件化
    • APP 架構(gòu)方式是單一工程模式缓屠,業(yè)務(wù)規(guī)模擴(kuò)大,隨之帶來(lái)的是團(tuán)隊(duì)規(guī)模擴(kuò)大坐搔,那就涉及到多人協(xié)作問(wèn)題藏研,每個(gè)移動(dòng)端軟件開(kāi)發(fā)人員勢(shì)必要熟悉如此之多代碼敬矩,如果不按照一定的模塊組件機(jī)制去劃分概行,將很難進(jìn)行多人協(xié)作開(kāi)發(fā),隨著單一項(xiàng)目變大弧岳,而且Andorid項(xiàng)目在編譯代碼方面就會(huì)變得非车拭Γ卡頓,在單一工程代碼耦合嚴(yán)重禽炬,每修改一處代碼后都需要重新編譯打包測(cè)試涧卵,導(dǎo)致非常耗時(shí)。

1.2 現(xiàn)階段遇到的問(wèn)題

  • 結(jié)合投資界腹尖,新芽客戶(hù)端分析
    • 代碼量膨脹柳恐,不利于維護(hù),不利于新功能的開(kāi)發(fā)热幔。項(xiàng)目工程構(gòu)建速度慢乐设,在一些電腦上寫(xiě)兩句代碼,重新編譯整個(gè)項(xiàng)目绎巨,測(cè)試的話(huà)編譯速度起碼 10-20 分鐘近尚,有的甚至更長(zhǎng)。
    • 不同模塊之間代碼耦合嚴(yán)重场勤,有時(shí)候修改一處代碼而牽動(dòng)許多模塊戈锻。每個(gè)模塊之間都有引用第三方庫(kù)歼跟,但有些第三方庫(kù)版本不一致,導(dǎo)致打包APP時(shí)候代碼冗余格遭,容易引起版本沖突哈街。
    • 現(xiàn)有項(xiàng)目基于以前其他人項(xiàng)目基礎(chǔ)上開(kāi)發(fā),經(jīng)手的人次過(guò)多如庭,存在著不同的代碼風(fēng)格叹卷,項(xiàng)目中代碼規(guī)范亂,類(lèi)似的功能寫(xiě)法卻不一樣坪它,導(dǎo)致不統(tǒng)一骤竹。

2.組件化的概念

2.1 什么是組件化

  • 什么是組件化呢?
    • 組件(Component)是對(duì)數(shù)據(jù)和方法的簡(jiǎn)單封裝往毡,功能單一蒙揣,高內(nèi)聚,并且是業(yè)務(wù)能劃分的最小粒度开瞭。
    • 組件化是基于組件可重用的目的上懒震,將一個(gè)大的軟件系統(tǒng)按照分離關(guān)注點(diǎn)的形式,拆分成多個(gè)獨(dú)立的組件嗤详,使得整個(gè)軟件系統(tǒng)也做到電路板一樣个扰,是單個(gè)或多個(gè)組件元件組裝起來(lái),哪個(gè)組件壞了葱色,整個(gè)系統(tǒng)可繼續(xù)運(yùn)行递宅,而不出現(xiàn)崩潰或不正常現(xiàn)象苍狰,做到更少的耦合和更高的內(nèi)聚办龄。

2.2 區(qū)分模塊化與組件化

  • 模塊化
    • 模塊化就是將一個(gè)程序按照其功能做拆分,分成相互獨(dú)立的模塊淋昭,以便于每個(gè)模塊只包含與其功能相關(guān)的內(nèi)容俐填,模塊我們相對(duì)熟悉,比如登錄功能可以是一個(gè)模塊,搜索功能可以是一個(gè)模塊等等。
  • 組件化
    • 組件化就是更關(guān)注可復(fù)用性翔忽,更注重關(guān)注點(diǎn)分離英融,如果從集合角度來(lái)看的話(huà),可以說(shuō)往往一個(gè)模塊包含了一個(gè)或多個(gè)組件歇式,或者說(shuō)模塊是一個(gè)容器驶悟,由組件組裝而成膨桥。簡(jiǎn)單來(lái)說(shuō)芯勘,組件化相比模塊化粒度更小,兩者的本質(zhì)思想都是一致的鬼佣,都是把大往小的方向拆分豺憔,都是為了復(fù)用和解耦额获,只不過(guò)模塊化更加側(cè)重于業(yè)務(wù)功能的劃分够庙,偏向于復(fù)用,組件化更加側(cè)重于單一功能的內(nèi)聚抄邀,偏向于解耦耘眨。

2.3 組件化優(yōu)勢(shì)好處

  • 簡(jiǎn)單來(lái)說(shuō)就是提高工作效率,解放生產(chǎn)力境肾,好處如下:
    • 1.提高編譯速度剔难,從而提高并行開(kāi)發(fā)效率。
      • 問(wèn)題:那么如何提高編譯速度的呢奥喻?組件化框架可以使模塊單獨(dú)編譯調(diào)試偶宫,可以有效地減少編譯的時(shí)間。
    • 2.穩(wěn)定的公共模塊采用依賴(lài)庫(kù)方式
      • 提供給各個(gè)業(yè)務(wù)線(xiàn)使用环鲤,減少重復(fù)開(kāi)發(fā)和維護(hù)工作量纯趋。代碼簡(jiǎn)潔,冗余量少冷离,維護(hù)方便吵冒,易擴(kuò)展新功能。
    • 3.每個(gè)組件有自己獨(dú)立的版本西剥,可以獨(dú)立編譯痹栖、測(cè)試、打包和部署瞭空。
      • 針對(duì)開(kāi)發(fā)程序員多的公司揪阿,組件化很有必要,每個(gè)人負(fù)責(zé)自己的模塊匙铡,可以較少提交代碼沖突图甜。
      • 為新業(yè)務(wù)隨時(shí)集成提供了基礎(chǔ)碍粥,所有業(yè)務(wù)可上可下鳖眼,靈活多變。
      • 各業(yè)務(wù)線(xiàn)研發(fā)可以互不干擾嚼摩、提升協(xié)作效率钦讳,并控制產(chǎn)品質(zhì)量。
    • 4.避免模塊之間的交叉依賴(lài)枕面,做到低耦合愿卒、高內(nèi)聚。
    • 5.引用的第三方庫(kù)代碼統(tǒng)一管理潮秘,避免版本統(tǒng)一琼开,減少引入冗余庫(kù)。
      • 這個(gè)可以創(chuàng)建一個(gè)公共的gradle管理的文件枕荞,比如一個(gè)項(xiàng)目有十幾個(gè)組件柜候,想要改下某個(gè)庫(kù)或者版本號(hào)搞动,總不至于一個(gè)個(gè)修改吧。這個(gè)時(shí)候提取公共十分有必要
    • 6.定制項(xiàng)目可按需加載渣刷,組件之間可以靈活組建鹦肿,快速生成不同類(lèi)型的定制產(chǎn)品。

2.4 區(qū)分組件化和插件化

  • 組件化和插件化的區(qū)別
    • 組件化不是插件化辅柴,插件化是在【運(yùn)行時(shí)】箩溃,而組件化是在【編譯時(shí)】。換句話(huà)說(shuō)碌嘀,插件化是基于多APK的涣旨,而組件化本質(zhì)上還是只有一個(gè) APK。
    • 組件化和插件化的最大區(qū)別(應(yīng)該也是唯一區(qū)別)就是組件化在運(yùn)行時(shí)不具備動(dòng)態(tài)添加和修改組件的功能股冗,但是插件化是可以的开泽。
  • 組件化的目標(biāo)
    • 組件化的目標(biāo)之一就是降低整體工程(app)與組件的依賴(lài)關(guān)系,缺少任何一個(gè)組件都是可以存在并正常運(yùn)行的魁瞪。app主工程具有和組件進(jìn)行綁定和解綁的功能穆律。

2.5 application和library

  • 在studio中,對(duì)兩種module進(jìn)行區(qū)分导俘,如下所示
    • 一種是基礎(chǔ)庫(kù)library峦耘,比如常見(jiàn)第三方庫(kù)都是lib,這些代碼被其他組件直接引用旅薄。
    • 另一種是application辅髓,也稱(chēng)之為Component,這種module是一個(gè)完整的功能模塊少梁。比如分享module就是一個(gè)Component洛口。
    • 為了方便,統(tǒng)一把library稱(chēng)之為依賴(lài)庫(kù)凯沪,而把Component稱(chēng)之為組件第焰,下面所講的組件化也主要是針對(duì)Component這種類(lèi)型。
  • 在項(xiàng)目的build.gradle文件中
    //控制組件模式和集成模式
    if (rootProject.ext.isDouBanApplication) {
        //是Component妨马,可以獨(dú)立運(yùn)行
        apply plugin: 'com.android.application'
    } else {
        //是lib挺举,被依賴(lài)
        apply plugin: 'com.android.library'
    }
    

3.創(chuàng)建組件化框架

3.1 傳統(tǒng)APP架構(gòu)圖

  • 傳統(tǒng)APP架構(gòu)圖
    • 如圖所示,從網(wǎng)上摘來(lái)的……
    • image
  • 存在的問(wèn)題
    • 普遍使用的 Android APP 技術(shù)架構(gòu)烘跺,往往是在一個(gè)界面中存在大量的業(yè)務(wù)邏輯湘纵,而業(yè)務(wù)邏輯中充斥著各種網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)操作等行為滤淳,整個(gè)項(xiàng)目中也沒(méi)有模塊的概念梧喷,只有簡(jiǎn)單的以業(yè)務(wù)邏輯劃分的文件夾,并且業(yè)務(wù)之間也是直接相互調(diào)用、高度耦合在一起的铺敌。單一工程模型下的業(yè)務(wù)關(guān)系绊困,總的來(lái)說(shuō)就是:你中有我,我中有你适刀,相互依賴(lài)秤朗,無(wú)法分離。如下圖:
    • image

3.2 組件化需要考慮問(wèn)題

  • 考慮的問(wèn)題
  • 分而治之笔喉,并行開(kāi)發(fā)取视,一切皆組件。要實(shí)現(xiàn)組件化常挚,無(wú)論采用什么樣的技術(shù)方式作谭,需要考慮以下七個(gè)方面問(wèn)題:
    • 代碼解耦。
      • 如何將一個(gè)龐大的工程分成有機(jī)的整體奄毡?這個(gè)需要一步步來(lái)了折欠!
      • 對(duì)已存在的項(xiàng)目進(jìn)行模塊拆分,模塊分為兩種類(lèi)型吼过,一種是功能組件模塊锐秦,封裝一些公共的方法服務(wù)等,作為依賴(lài)庫(kù)對(duì)外提供盗忱;另一種是業(yè)務(wù)組件模塊酱床,專(zhuān)門(mén)處理業(yè)務(wù)邏輯等功能,這些業(yè)務(wù)組件模塊最終負(fù)責(zé)組裝APP趟佃。
    • 組件單獨(dú)運(yùn)行扇谣。
      • 因?yàn)槊總€(gè)組件都是高度內(nèi)聚的,是一個(gè)完整的整體闲昭,如何讓其單獨(dú)運(yùn)行和調(diào)試罐寨?
      • 通過(guò) Gradle腳本配置方式,進(jìn)行不同環(huán)境切換序矩,我自己操作是添加一個(gè)boolean值的開(kāi)關(guān)鸯绿。比如只需要把 Apply plugin: 'com.android.library' 切換成Apply plugin: 'com.android.application' 就可以獨(dú)立運(yùn)行呢!
      • 需要注意:當(dāng)切換到application獨(dú)立運(yùn)行時(shí)贮泞,需要在AndroidManifest清單文件上進(jìn)行設(shè)置楞慈,因?yàn)橐粋€(gè)單獨(dú)調(diào)試需要有一個(gè)入口的Activity幔烛。
    • 組件間通信啃擦。
      • 由于每個(gè)組件具體實(shí)現(xiàn)細(xì)節(jié)都互相不了解,但每個(gè)組件都需要給其他調(diào)用方提供服務(wù)饿悬,那么主項(xiàng)目與組件令蛉、組件與組件之間如何通信就變成關(guān)鍵?
      • 這個(gè)我是直接用阿里開(kāi)源的路由框架,當(dāng)然你可以根據(jù)需要選擇其他大廠(chǎng)的開(kāi)源路由庫(kù)珠叔。引用阿里的ARouter框架蝎宇,通過(guò)注解方式進(jìn)行頁(yè)面跳轉(zhuǎn)。
    • 組件生命周期祷安。
      • 這里的生命周期指的是組件在應(yīng)用中存在的時(shí)間姥芥,組件是否可以做到按需、動(dòng)態(tài)使用汇鞭、因此就會(huì)涉及到組件加載凉唐、卸載等管理問(wèn)題。
    • 集成調(diào)試霍骄。
      • 在開(kāi)發(fā)階段如何做到按需編譯組件台囱?一次調(diào)試中可能有一兩個(gè)組件參與集成,這樣編譯時(shí)間就會(huì)大大降低读整,提高開(kāi)發(fā)效率簿训。
    • 代碼隔離。
      • 組件之間的交互如果還是直接引用的話(huà)米间,那么組件之間根本沒(méi)有做到解耦强品,如何從根本上避免組件之間的直接引用?目前做法是主項(xiàng)目和業(yè)務(wù)組件都會(huì)依賴(lài)公共基礎(chǔ)組件庫(kù)屈糊,業(yè)務(wù)組件通過(guò)路由服務(wù)依賴(lài)庫(kù)按需進(jìn)行查找择懂,用于不同組件之間的通信。
    • 告別結(jié)構(gòu)臃腫另玖,讓各個(gè)業(yè)務(wù)變得相對(duì)獨(dú)立困曙,業(yè)務(wù)組件在組件模式下可以獨(dú)立開(kāi)發(fā),而在集成模式下又可以變?yōu)锳AR包集成到“APP殼工程”中谦去,組成一個(gè)完整功能的 APP慷丽。

3.3 架構(gòu)設(shè)計(jì)圖

  • 組件化架構(gòu)圖
    • 業(yè)務(wù)組件之間是獨(dú)立的,互相沒(méi)有關(guān)聯(lián)鳄哭,這些業(yè)務(wù)組件在集成模式下是一個(gè)個(gè) Library要糊,被 APP 殼工程所依賴(lài),組成一個(gè)具有完整業(yè)務(wù)功能的 APP 應(yīng)用妆丘,但是在組件開(kāi)發(fā)模式下锄俄,業(yè)務(wù)組件又變成了一個(gè)個(gè)Application,它們可以獨(dú)立開(kāi)發(fā)和調(diào)試勺拣,由于在組件開(kāi)發(fā)模式下奶赠,業(yè)務(wù)組件們的代碼量相比于完整的項(xiàng)目差了很遠(yuǎn),因此在運(yùn)行時(shí)可以顯著減少編譯時(shí)間药有。
    • image

3.4 組件通信是通過(guò)路由轉(zhuǎn)發(fā)

  • 傳統(tǒng)以前工程下模塊
    • 記得剛開(kāi)始進(jìn)入Android開(kāi)發(fā)工作時(shí)毅戈,只有一個(gè)app主工程苹丸,后期幾乎所有的需求都寫(xiě)在這個(gè)app主工程里面。只有簡(jiǎn)單的以業(yè)務(wù)邏輯劃分的文件夾苇经,并且業(yè)務(wù)之間也是直接相互調(diào)用赘理、高度耦合在一起的。
    • 導(dǎo)致后期改項(xiàng)目為組件化的時(shí)候十分痛苦扇单,不同模塊之間的業(yè)務(wù)邏輯實(shí)在關(guān)聯(lián)太多商模,但還是沒(méi)辦法,于是目錄4步驟一步步實(shí)踐蜘澜。終極目標(biāo)是阻桅,告別結(jié)構(gòu)臃腫,讓各個(gè)業(yè)務(wù)變得相對(duì)獨(dú)立兼都,業(yè)務(wù)組件在組件模式下可以獨(dú)立開(kāi)發(fā)嫂沉。
  • 組件化模式下如何通信
    • 這是組件化工程模型下的業(yè)務(wù)關(guān)系,業(yè)務(wù)之間將不再直接引用和依賴(lài)扮碧,而是通過(guò)“路由”這樣一個(gè)中轉(zhuǎn)站間接產(chǎn)生聯(lián)系趟章。在這個(gè)開(kāi)源項(xiàng)目中,我使用的阿里開(kāi)源的路由框架慎王。關(guān)于Arouter基礎(chǔ)使用和代碼分析蚓土,可以看我這篇博客:Arouter使用與代碼解析
    • image

3.6 業(yè)務(wù)組件的生命周期

  • 按照理想狀態(tài)的來(lái)看待的話(huà)
    • 各個(gè)業(yè)務(wù)組件之間沒(méi)有任何依賴(lài)關(guān)系,這時(shí)我們可以把每個(gè)獨(dú)立的業(yè)務(wù)組件看成一個(gè)可運(yùn)行的app赖淤,所以業(yè)務(wù)組件的生命周期和應(yīng)與獨(dú)立的app保持一致蜀漆。

3.7 Fragment通信難點(diǎn)

  • 在網(wǎng)上看到很多博客說(shuō),如何拆分組件咱旱,按模塊拆分确丢,或者按照功能拆分。但很少有提到fragment在拆分組件時(shí)的疑問(wèn)吐限,這個(gè)讓我很奇怪鲜侥。
  • 先來(lái)說(shuō)一個(gè)業(yè)務(wù)需求,比如一個(gè)購(gòu)物商城app诸典,有4個(gè)模塊描函,做法一般是一個(gè)activity+4個(gè)fragment,這個(gè)大家都很熟悉狐粱,這四個(gè)模塊分別是:首頁(yè)舀寓,發(fā)現(xiàn),購(gòu)物車(chē)肌蜻,我的互墓。然后這幾個(gè)頁(yè)面是用fragment寫(xiě)的,共用一個(gè)宿主activity宋欺,那么在做組件化的時(shí)候轰豆,我想把它按照業(yè)務(wù)拆分成首頁(yè)胰伍,發(fā)現(xiàn)齿诞,購(gòu)物車(chē)和我的四個(gè)獨(dú)立的業(yè)務(wù)模塊酸休。
  • 遇到疑問(wèn):
    • 如果是拆分成四個(gè)獨(dú)立的業(yè)務(wù)模塊,那么對(duì)應(yīng)的fragment肯定要放到對(duì)應(yīng)的組件中祷杈,那么這樣操作斑司,當(dāng)主工程與該業(yè)務(wù)組件解綁的情況下,如何拿到fragment和傳遞參數(shù)進(jìn)行通信但汞。
    • Fragment 中 開(kāi)啟Activity帶requestCode宿刮,開(kāi)啟的Activity關(guān)閉后,不會(huì)回調(diào)Fragment中的onActivityResult。只會(huì)調(diào)用Fragment 所在Activity的onActivityResult私蕾。
    • 多fragment單activity攔截器不管用僵缺,難道只能用于攔截activity的跳轉(zhuǎn)?那如果是要實(shí)現(xiàn)登錄攔截的話(huà)踩叭,那不是只能在PathReplaceService中進(jìn)行了吼野?
  • 網(wǎng)絡(luò)解決辦法
    • 第一個(gè)疑問(wèn):由于我使用阿里路由贪庙,所以我看到zhi1ong大佬說(shuō):用Router跳轉(zhuǎn)到這個(gè)Activity,然后帶一個(gè)參數(shù)進(jìn)去,比方說(shuō)tab=2屈留,然后自己在onCreate里面自行切換。但后來(lái)嘗試葵诈,還是想問(wèn)問(wèn)廣大程序員有沒(méi)有更好的辦法罐柳。
    • 第二個(gè)疑問(wèn):還是zhi1ong大佬說(shuō),通過(guò)廣播满力,或者在Activity中轉(zhuǎn)發(fā)這個(gè)事件焕参,比方說(shuō)讓Fragment統(tǒng)一依賴(lài)一個(gè)接口,然后在Activity中轉(zhuǎn)發(fā)油额。

4.實(shí)際開(kāi)發(fā)案例

4.1 組件化實(shí)踐的開(kāi)源項(xiàng)目

  • 關(guān)于組件化開(kāi)發(fā)一點(diǎn)感想
    • 關(guān)于網(wǎng)上有許多關(guān)于組件化的博客龟糕,講解了什么是組件化,為何要組件化悔耘,以及組件化的好處讲岁。大多數(shù)文章提供了組件化的思路,給我著手組件化開(kāi)發(fā)提供了大量的便利衬以。感謝前輩大神的分享缓艳!雖然有一些收獲,但是很少有文章能夠給出一個(gè)整體且有效的方案看峻,或者一個(gè)具體的Demo阶淘。
    • 但是畢竟看博客也是為了實(shí)踐做準(zhǔn)備,當(dāng)著手將之前的開(kāi)源案例改版成組件化案例時(shí)互妓,出現(xiàn)了大量的問(wèn)題溪窒,也解決了一些問(wèn)題坤塞。主要是學(xué)些了組件化開(kāi)發(fā)流程。
    • 大多數(shù)公司慢慢著手組件化開(kāi)發(fā)澈蚌,在小公司摹芙,有的人由于之前沒(méi)有做過(guò)組件化開(kāi)發(fā),嘗試組件化也是挺好的宛瞄;在大公司浮禾,有的人一去只是負(fù)責(zé)某個(gè)模塊,可能剛開(kāi)始組件化已經(jīng)有人弄好了份汗,那學(xué)習(xí)實(shí)踐組件化那更快一些盈电。業(yè)余實(shí)踐,改版之前開(kāi)源項(xiàng)目杯活,寫(xiě)了這篇博客匆帚,耗費(fèi)我不少時(shí)間,要是對(duì)你有些幫助旁钧,那我就很開(kāi)心呢吸重。由于我也是個(gè)小人物,所以寫(xiě)的不好均践,勿噴晤锹,歡迎提出建議!
  • 關(guān)于組件化開(kāi)源項(xiàng)目
    • 項(xiàng)目整體架構(gòu)模式采用:組件化+MVP+Rx+Retrofit+design+Dagger2+VLayout+X5
    • 包含的模塊:wanAndroid【kotlin】+干貨集中營(yíng)+知乎日?qǐng)?bào)+番茄Todo+精選新聞+豆瓣音樂(lè)電影小說(shuō)+小說(shuō)讀書(shū)+簡(jiǎn)易記事本+搞笑視頻+經(jīng)典游戲+其他更多等等
    • 此項(xiàng)目屬于業(yè)余時(shí)間練手的項(xiàng)目彤委,接口數(shù)據(jù)來(lái)源均來(lái)自網(wǎng)絡(luò)鞭铆,如果存在侵權(quán)情況,請(qǐng)第一時(shí)間告知焦影。本項(xiàng)目?jī)H做學(xué)習(xí)交流使用车遂,API數(shù)據(jù)內(nèi)容所有權(quán)歸原作公司所有,請(qǐng)勿用于其他用途斯辰。
    • 關(guān)于開(kāi)源組件化的項(xiàng)目地址:https://github.com/yangchong211/LifeHelper

4.1 如何創(chuàng)建模塊

  • 根據(jù)3.3 架構(gòu)設(shè)計(jì)圖可以知道
  • 主工程:
    • 除了一些全局配置和主 Activity 之外舶担,不包含任何業(yè)務(wù)代碼。有的也叫做空殼app彬呻,主要是依賴(lài)業(yè)務(wù)組件進(jìn)行運(yùn)行衣陶。
  • 業(yè)務(wù)組件:
    • 最上層的業(yè)務(wù),每個(gè)組件表示一條完整的業(yè)務(wù)線(xiàn)闸氮,彼此之間互相獨(dú)立剪况。原則上來(lái)說(shuō):各個(gè)業(yè)務(wù)組件之間不能有直接依賴(lài)!所有的業(yè)務(wù)組件均需要可以做到獨(dú)立運(yùn)行的效果蒲跨。對(duì)于測(cè)試的時(shí)候译断,需要依賴(lài)多個(gè)業(yè)務(wù)組件的功能進(jìn)行集成測(cè)試的時(shí)候』虮可以使用app殼進(jìn)行多組件依賴(lài)管理運(yùn)行孙咪。
    • 該案例中分為:干活集中營(yíng)堪唐,玩Android,知乎日?qǐng)?bào)翎蹈,微信新聞淮菠,頭條新聞,搞笑視頻杨蛋,百度音樂(lè)兜材,我的記事本理澎,豆瓣音樂(lè)讀書(shū)電影逞力,游戲組件等等。
  • 功能組件:
    • 該案例中分為糠爬,分享組件寇荧,評(píng)論反饋組件,支付組件执隧,畫(huà)廊組件等等揩抡。同時(shí)注意,可能會(huì)涉及多個(gè)業(yè)務(wù)組件對(duì)某個(gè)功能組件進(jìn)行依賴(lài)镀琉!
  • 基礎(chǔ)組件:
    • 支撐上層業(yè)務(wù)組件運(yùn)行的基礎(chǔ)業(yè)務(wù)服務(wù)峦嗤。此部分組件為上層業(yè)務(wù)組件提供基本的功能支持。
    • 該案例中:在基礎(chǔ)組件庫(kù)中主要有屋摔,網(wǎng)絡(luò)請(qǐng)求烁设,圖片加載,通信機(jī)制钓试,工具類(lèi)装黑,分享功能,支付功能等等弓熏。當(dāng)然恋谭,我把一些公共第三方庫(kù)放到了這個(gè)基礎(chǔ)組件中!

4.2 如何建立依賴(lài)

  • 關(guān)于工程中組件依賴(lài)結(jié)構(gòu)圖如下所示
    • image
  • 業(yè)務(wù)模塊下完整配置代碼
    //控制組件模式和集成模式
    if (rootProject.ext.isGankApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
    
            if (rootProject.ext.isGankApplication){
                //組件模式下設(shè)置applicationId
                applicationId "com.ycbjie.gank"
            }
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [AROUTER_MODULE_NAME: project.getName()]
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
        //jdk1.8
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        sourceSets {
            main {
                if (rootProject.ext.isGankApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation project(':library')
        annotationProcessor rootProject.ext.dependencies["router-compiler"]
    }
    

4.3 如何統(tǒng)一配置文件

  • 由于組件化實(shí)踐中模塊比較多挽鞠,因此配置gradle疚颊,添加依賴(lài)庫(kù)時(shí),需要考慮簡(jiǎn)化工作信认。那么究竟如何做呢材义?
  • 第一步,首先在項(xiàng)目根目錄下創(chuàng)建一個(gè)yc.gradle文件狮杨。實(shí)際開(kāi)發(fā)中只需要更改該文件中版本信息即可母截。
    • 我在網(wǎng)上看到的絕大多數(shù)案例,都是通過(guò)一個(gè)開(kāi)關(guān)控件組件模式和集成模式的切換橄教,但是這里我配置了多個(gè)組件的開(kāi)關(guān)清寇,分別控制對(duì)應(yīng)的組件切換狀態(tài)喘漏。
    ext {
    
        isApplication = false  //false:作為L(zhǎng)ib組件存在, true:作為application存在
        isAndroidApplication = false  //玩Android模塊開(kāi)關(guān)华烟,false:作為L(zhǎng)ib組件存在翩迈, true:作為application存在
        isLoveApplication = false  //愛(ài)意表達(dá)模塊開(kāi)關(guān),false:作為L(zhǎng)ib組件存在盔夜, true:作為application存在
        isVideoApplication = false  //視頻模塊開(kāi)關(guān)负饲,false:作為L(zhǎng)ib組件存在, true:作為application存在
        isNoteApplication = false  //記事本模塊開(kāi)關(guān)喂链,false:作為L(zhǎng)ib組件存在返十, true:作為application存在
        isBookApplication = false  //book模塊開(kāi)關(guān),false:作為L(zhǎng)ib組件存在椭微, true:作為application存在
        isDouBanApplication = false  //豆瓣模塊開(kāi)關(guān)洞坑,false:作為L(zhǎng)ib組件存在, true:作為application存在
        isGankApplication = false  //干貨模塊開(kāi)關(guān)蝇率,false:作為L(zhǎng)ib組件存在迟杂, true:作為application存在
        isMusicApplication = false  //音樂(lè)模塊開(kāi)關(guān),false:作為L(zhǎng)ib組件存在本慕, true:作為application存在
        isNewsApplication = false  //新聞模塊開(kāi)關(guān)排拷,false:作為L(zhǎng)ib組件存在, true:作為application存在
        isToDoApplication = false  //todo模塊開(kāi)關(guān)锅尘,false:作為L(zhǎng)ib組件存在监氢, true:作為application存在
        isZhiHuApplication = false  //知乎模塊開(kāi)關(guān),false:作為L(zhǎng)ib組件存在鉴象, true:作為application存在
        isOtherApplication = false  //其他模塊開(kāi)關(guān)忙菠,false:作為L(zhǎng)ib組件存在, true:作為application存在
        
        android = [
                   compileSdkVersion       : 28,
                   buildToolsVersion       : "28.0.3",
                   minSdkVersion           : 17,
                   targetSdkVersion        : 28,
                   versionCode             : 22,
                   versionName             : "1.8.2"    //必須是int或者float纺弊,否則影響線(xiàn)上升級(jí)
        ]
    
        version = [
                   androidSupportSdkVersion: "28.0.0",
                   retrofitSdkVersion      : "2.4.0",
                   glideSdkVersion         : "4.8.0",
                   canarySdkVersion        : "1.5.4",
                   constraintVersion       : "1.0.2"
        ]
    
        dependencies = [
                    //support
                    "appcompat-v7"             : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
                    "multidex"                 : "com.android.support:multidex:1.0.1",
                    //network
                    "retrofit"                 : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
                    "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
                    "retrofit-adapter-rxjava"  : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
                    //這里省略一部分代碼
            ]
    }
    
  • 第二步牛欢,然后在項(xiàng)目中的lib【注意這里是放到基礎(chǔ)組件庫(kù)的build.gradle】中添加代碼,如下所示
    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
        }
    }
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        api rootProject.ext.dependencies["appcompat-v7"]
        api rootProject.ext.dependencies["design"]
        api rootProject.ext.dependencies["palette"]
        api rootProject.ext.dependencies["glide"]
        api (rootProject.ext.dependencies["glide-transformations"]){
            exclude module: 'glide'
        }
        annotationProcessor rootProject.ext.dependencies["glide-compiler"]
        api files('libs/tbs_sdk_thirdapp_v3.2.0.jar')
        api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        
        //省略部分代碼
    }
    
  • 第三步淆游,在其他model中添加依賴(lài)
    • implementation project(':library')即可傍睹。

4.4 組件化的基礎(chǔ)庫(kù)

  • 基礎(chǔ)庫(kù)組件封裝
    • 基礎(chǔ)庫(kù)組件封裝庫(kù)中主要包括開(kāi)發(fā)常用的一些框架∮塘猓可以直接看我的項(xiàng)目更加直觀拾稳!
    • 1、網(wǎng)絡(luò)請(qǐng)求(采用Retrofit+RxJava框架)腊脱,攔截器
    • 2访得、圖片加載(策略模式,Glide與Picasso之間可以切換)
    • 3、通信機(jī)制(RxBus)悍抑,路由ARouter簡(jiǎn)單封裝工具類(lèi)(不同model之間通信)
    • 4鳄炉、mvp框架,常用的base類(lèi)搜骡,比如BaseActivity拂盯,BaseFragment等等
    • 5、通用的工具類(lèi)记靡,比如切割圓角谈竿,動(dòng)畫(huà)工具類(lèi)等等
    • 6、自定義view(包括對(duì)話(huà)框摸吠,ToolBar布局空凸,圓形圖片等view的自定義)
    • 7、共有的shape蜕便,drawable劫恒,layout贩幻,color等資源文件
    • 8轿腺、全局初始化異步線(xiàn)程池封裝庫(kù),各個(gè)組件均可以用到
  • 組件初始化
    • 比如丛楚,你將該案例中的新聞組件切換成獨(dú)立運(yùn)行的app族壳,那么由于新聞跳轉(zhuǎn)詳情頁(yè)需要使用到x5的WebView,因此需要對(duì)它進(jìn)行初始化趣些。最剛開(kāi)始做法是仿荆,為每一個(gè)可以切換成app的組件配置一個(gè)獨(dú)立的application,然后初始化一些該組件需要初始化的任務(wù)坏平。但是這么做拢操,有一點(diǎn)不好,不是很方便管理舶替。后來(lái)看了知乎組件化實(shí)踐方案后令境,該方案提出,開(kāi)發(fā)了一套多線(xiàn)程初始化框架顾瞪,每個(gè)組件只要新建若干個(gè)啟動(dòng) Task 類(lèi)舔庶,并在 Task 中聲明依賴(lài)關(guān)系。但是具體怎么用到代碼中后期有待實(shí)現(xiàn)陈醒!
  • 如何簡(jiǎn)化不熟悉組件化的人快速適應(yīng)組件獨(dú)立運(yùn)行
    • 設(shè)置多個(gè)組件開(kāi)關(guān)惕橙,需要切換那個(gè)組件就改那個(gè)。如果設(shè)置一個(gè)開(kāi)關(guān)钉跷,要么把所有組件切成集成模式弥鹦,要么把所有組件切成組件模式,有點(diǎn)容易出問(wèn)題爷辙。更多可以往下看彬坏!
  • 嚴(yán)格限制公共基礎(chǔ)組件的增長(zhǎng)
    • 隨著開(kāi)發(fā)不斷進(jìn)行吼虎,要注意不要往基礎(chǔ)公共組件加入太多內(nèi)容。而是應(yīng)該減小體積苍鲜!倘若是基礎(chǔ)組件過(guò)于龐大思灰,那么運(yùn)行組件也是比較緩慢的!

4.5 組件模式和集成模式如何切換

  • 在玩Android組件下的build.gradle文件混滔,其他組件類(lèi)似洒疚。
    • 通過(guò)一個(gè)開(kāi)關(guān)來(lái)控制這個(gè)狀態(tài)的切換,module如果是一個(gè)庫(kù)坯屿,會(huì)使用com.android.library插件油湖;如果是一個(gè)應(yīng)用,則使用com.android.application插件
    //控制組件模式和集成模式
    if (rootProject.ext.isAndroidApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
  • 集成模式如下所示
    • 首先需要在yc.gradle文件中設(shè)置 isApplication=false领跛。Sync下后乏德,發(fā)現(xiàn)該組件是library
    ext {
        isAndroidApplication = false  //false:作為L(zhǎng)ib組件存在, true:作為application存在
    
  • 組件模式如下所示
    • 首先需要在yc.gradle文件中設(shè)置 isApplication=true吠昭。Sync下后喊括,發(fā)現(xiàn)該組件是application,即可針對(duì)模塊進(jìn)行運(yùn)行
    ext {
        isAndroidApplication = true  //false:作為L(zhǎng)ib組件存在矢棚, true:作為application存在
    
  • 需要注意的地方郑什,這個(gè)很重要
    • 首先看看網(wǎng)上絕大多數(shù)的作法,非常感謝這些大神的無(wú)私奉獻(xiàn)蒲肋!但是我覺(jué)得多個(gè)組件用一個(gè)開(kāi)關(guān)控制也可以蘑拯,但是sourceSets里面切換成組件app時(shí),可以直接不用下面這么麻煩兜粘,可以復(fù)用java和res文件申窘。
      • image
    • 接下來(lái)看看我的做法:
    • 下面這個(gè)配置十分重要。也就是說(shuō)當(dāng)該玩Android組件從library切換到application時(shí)孔轴,由于可以作為獨(dú)立app運(yùn)行剃法,所以序意設(shè)置applicationId,并且配置清單文件距糖,如下所示玄窝!
    • 在 library 和 application 之間切換,manifest文件也需要提供兩套
    android {
        defaultConfig {
            if (rootProject.ext.isAndroidApplication){
                //組件模式下設(shè)置applicationId
                applicationId "com.ycbjie.android"
            }
        }
        sourceSets {
            main {
                if (rootProject.ext.isAndroidApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    }
    
    • 具體在項(xiàng)目中如下所示
    • image

4.6 組件化解決重復(fù)依賴(lài)

  • 重復(fù)依賴(lài)問(wèn)題說(shuō)明
    • 重復(fù)依賴(lài)問(wèn)題其實(shí)在開(kāi)發(fā)中經(jīng)常會(huì)遇到悍引,比如項(xiàng)目 implementation 了一個(gè)A恩脂,然后在這個(gè)庫(kù)里面又 implementation 了一個(gè)B,然后你的工程中又 implementation 了一個(gè)同樣的B趣斤,就依賴(lài)了兩次俩块。
    • 默認(rèn)情況下,如果是 aar 依賴(lài),gradle 會(huì)自動(dòng)幫我們找出新版本的庫(kù)而拋棄舊版本的重復(fù)依賴(lài)玉凯。但是如果使用的是project依賴(lài)势腮,gradle并不會(huì)去去重,最后打包就會(huì)出現(xiàn)代碼中有重復(fù)的類(lèi)了漫仆。
  • 解決辦法捎拯,舉個(gè)例子
    api(rootProject.ext.dependencies["logger"]) { 
        exclude module: 'support-v4'//根據(jù)組件名排除 
        exclude group: 'android.support.v4'//根據(jù)包名排除 
    }
    

4.7 組件化注意要點(diǎn)

  • 業(yè)務(wù)組件之間聯(lián)動(dòng)導(dǎo)致耦合嚴(yán)重
    • 比如,實(shí)際開(kāi)發(fā)中盲厌,購(gòu)物車(chē)和首頁(yè)商品分別是兩個(gè)組件署照。但是遇到產(chǎn)品需求,比如過(guò)節(jié)做個(gè)活動(dòng)吗浩,發(fā)個(gè)購(gòu)物券之類(lèi)的需求建芙,由于購(gòu)物車(chē)和商品詳情頁(yè)都有活動(dòng),因此會(huì)造成組件經(jīng)常會(huì)發(fā)生聯(lián)動(dòng)懂扼。倘若前期準(zhǔn)備不足禁荸,隨著時(shí)間的推移站粟,各個(gè)業(yè)務(wù)線(xiàn)的代碼邊界會(huì)像組件化之前的主工程一樣逐漸劣化诉稍,耦合會(huì)越來(lái)越嚴(yán)重。
    • 第一種解決方式:使用 sourceSets 的方式將不同的業(yè)務(wù)代碼放到不同的文件夾压昼,但是 sourceSets 的問(wèn)題在于炕倘,它并不能限制各個(gè) sourceSet 之間互相引用,所以這種方式并不太友好!
    • 第二種解決方式:抽取需求為工具類(lèi)宪潮,通過(guò)不同組件傳值而達(dá)到調(diào)用關(guān)系尽棕,這樣只需要改工具類(lèi)即可改需求。但是這種只是符合需求一樣,但是用在不同模塊的場(chǎng)景弯屈。
  • 組件化開(kāi)發(fā)之?dāng)?shù)據(jù)庫(kù)分離
    • 比如湘捎,我現(xiàn)在開(kāi)發(fā)的視頻模塊想要給別人用活翩,由于緩存之類(lèi)需要用到數(shù)據(jù)庫(kù),難道還要把這個(gè)lib還得依賴(lài)一個(gè)體積較大的第三方數(shù)據(jù)庫(kù)急灭?但是使用系統(tǒng)原生sql數(shù)據(jù)庫(kù)又不太方便扫尖,怎么辦?暫時(shí)我也沒(méi)找到辦法……

4.8 組件化時(shí)資源名沖突

  • 資源名沖突有哪些彻坛?
    • 比如,color煮嫌,shape,drawable灶轰,圖片資源谣沸,布局資源,或者anim資源等等笋颤,都有可能造成資源名稱(chēng)沖突乳附。這是為何了内地,有時(shí)候大家負(fù)責(zé)不同的模塊,如果不是按照統(tǒng)一規(guī)范命名赋除,則會(huì)偶發(fā)出現(xiàn)該問(wèn)題阱缓。
    • 尤其是如果string, color举农,dimens這些資源分布在了代碼的各個(gè)角落荆针,一個(gè)個(gè)去拆,非常繁瑣颁糟。其實(shí)大可不必這么做航背。因?yàn)閍ndroid在build時(shí),會(huì)進(jìn)行資源的merge和shrink棱貌。res/values下的各個(gè)文件(styles.xml需注意)最后都只會(huì)把用到的放到intermediate/res/merged/../valus.xml玖媚,無(wú)用的都會(huì)自動(dòng)刪除。并且最后我們可以使用lint來(lái)自動(dòng)刪除婚脱。所以這個(gè)地方不要耗費(fèi)太多的時(shí)間今魔。
  • 解決辦法
    • 這個(gè)問(wèn)題也不是新問(wèn)題了,第三方SDK基本都會(huì)遇到起惕,可以通過(guò)設(shè)置 resourcePrefix 來(lái)避免涡贱。設(shè)置了這個(gè)值后,你所有的資源名必須以指定的字符串做前綴惹想,否則會(huì)報(bào)錯(cuò)问词。但是 resourcePrefix 這個(gè)值只能限定 xml 里面的資源,并不能限定圖片資源嘀粱,所有圖片資源仍然需要你手動(dòng)去修改資源名激挪。
  • 個(gè)人建議
    • 將color,shape等放到基礎(chǔ)庫(kù)組件中锋叨,因?yàn)樗械臉I(yè)務(wù)組件都會(huì)依賴(lài)基礎(chǔ)組件庫(kù)垄分。在styles.xml需注意,寫(xiě)屬性名字的時(shí)候娃磺,一定要加上前綴限定詞薄湿。假如說(shuō)不加的話(huà),有可能會(huì)在打包成aar后給其他模塊使用的時(shí)候偷卧,會(huì)出現(xiàn)屬性名名字重復(fù)的沖突豺瘤,為什么呢?因?yàn)锽ezelImageView這個(gè)名字根本不會(huì)出現(xiàn)在intermediate/res/merged/../valus.xml里听诸, 所以不要以為這是屬性的限定詞坐求!

4.9 組件化開(kāi)發(fā)遇到問(wèn)題

  • 如何做到各個(gè)組件化模塊能獲取到全局上下文
    • 情景再現(xiàn)
      • 比如,剛開(kāi)始線(xiàn)上項(xiàng)目是在app主工程里創(chuàng)建的單利晌梨,那么在lib中或者后期劃分的組件化桥嗤,是無(wú)法拿到主工程的application類(lèi)中的上下文须妻。這個(gè)時(shí)候可以
    • 解決辦法
      • 很容易,在lib里寫(xiě)一個(gè)Utils工具類(lèi)泛领,然后在主工程application中初始化Utils.init(this)荒吏,這樣就可以在lib和所有業(yè)務(wù)組件[已經(jīng)依賴(lài)公共基礎(chǔ)組件庫(kù)]中拿到全局上下文呢!
  • butterKnife使用問(wèn)題
    • 盡管網(wǎng)上有不少博客說(shuō)可以解決butterKnife在不同組件之間的引用师逸。但是我在實(shí)際開(kāi)發(fā)時(shí)司倚,遇到組件模式和集成模式切換狀態(tài)時(shí),導(dǎo)致出現(xiàn)編譯錯(cuò)誤問(wèn)題篓像。要是那位在組件化中解決butterKnife引用問(wèn)題动知,可以告訴我,非常感謝员辩!
  • 當(dāng)組件化是lib時(shí)
    • 不能使用switch(R.id.xx),需要使用if..else來(lái)代替盒粮。
  • 不要亂發(fā)bus消息
    • 如果項(xiàng)目中大量的使用eventbus,那么會(huì)看到一個(gè)類(lèi)中有大量的onEventMainThread()方法奠滑,寫(xiě)起來(lái)很爽丹皱,閱讀起來(lái)很痛苦。
    • 雖然說(shuō)宋税,前期使用EventBus或者RxBus發(fā)送消息來(lái)實(shí)現(xiàn)組件間通信十分方便和簡(jiǎn)單摊崭,但是隨著業(yè)務(wù)增大,和后期不斷更新杰赛,有的還經(jīng)過(guò)多個(gè)程序員前前后后修改呢簸,會(huì)使代碼閱讀量降低。項(xiàng)目中發(fā)送這個(gè)Event的地方非常多乏屯,接收這個(gè)Event的地方也很多根时。在后期想要改進(jìn)為組件化開(kāi)發(fā),而進(jìn)行代碼拆分時(shí)辰晕,都不敢輕舉妄動(dòng)蛤迎,生怕哪些事件沒(méi)有被接收。
  • 頁(yè)面跳轉(zhuǎn)存在問(wèn)題
    • 如果一個(gè)頁(yè)面需要登陸狀態(tài)才可以查看含友,那么會(huì)寫(xiě)if(isLogin()){//跳轉(zhuǎn)頁(yè)面}else{//跳轉(zhuǎn)到登錄頁(yè)面}替裆,每次操作都要寫(xiě)這些個(gè)相同的邏輯。
    • 原生startActivity跳轉(zhuǎn)窘问,無(wú)法監(jiān)聽(tīng)到跳轉(zhuǎn)的狀態(tài)扎唾,比如跳轉(zhuǎn)錯(cuò)誤,成功南缓,異常等問(wèn)題。
    • 后時(shí)候荧呐,后臺(tái)會(huì)控制從點(diǎn)擊按鈕【不同場(chǎng)景下】跳轉(zhuǎn)到不同的頁(yè)面汉形,假如后臺(tái)配置信息錯(cuò)誤纸镊,或者少了參數(shù),那么跳轉(zhuǎn)可能不成功或者導(dǎo)致崩潰概疆,這個(gè)也沒(méi)有一個(gè)好的處理機(jī)制逗威。
    • 阿里推出的開(kāi)源框架Arouter,便可以解決頁(yè)面跳轉(zhuǎn)問(wèn)題岔冀,可以添加攔截凯旭,或者即使后臺(tái)配置參數(shù)錯(cuò)誤,當(dāng)監(jiān)聽(tīng)到跳轉(zhuǎn)異呈固祝或者跳轉(zhuǎn)錯(cuò)誤時(shí)的狀態(tài)罐呼,可以直接默認(rèn)跳轉(zhuǎn)到首頁(yè)。我在該開(kāi)源案例就是這么做的侦高!
  • 關(guān)于跳轉(zhuǎn)參數(shù)問(wèn)題
    • 先來(lái)看一下這種代碼寫(xiě)法嫉柴,這種寫(xiě)法本沒(méi)有問(wèn)題,只是在多人開(kāi)發(fā)時(shí)奉呛,如果別人想要跳轉(zhuǎn)到你開(kāi)發(fā)模塊的某個(gè)頁(yè)面计螺,那么就容易傳錯(cuò)值。建議將key這個(gè)值瞧壮,寫(xiě)成靜態(tài)常量登馒,放到一個(gè)專(zhuān)門(mén)的類(lèi)中。方便自己咆槽,也方便他人陈轿。
      //跳轉(zhuǎn)
      intent.setClass(this,CommentActivity.class);
      intent.putExtra("id",id);
      intent.putExtra("allNum",allNum);
      intent.putExtra("shortNum",shortNum);
      intent.putExtra("longNum",longNum);
      startActivity(intent);
      
      
      //接收
      Intent intent = getIntent();
      int allNum = intent.getExtras().getInt("allNum");
      int shortNum = intent.getExtras().getInt("shortNum");
      int longNum = intent.getExtras().getInt("longNum");
      int id = intent.getExtras().getInt("id");
      

5.組件間通信

5.1 選擇那個(gè)開(kāi)源路由庫(kù)

  • 比較有代表性的組件化開(kāi)源框架有得到得到DDComponentForAndroid、阿里Arouter罗晕、聚美Router 等等济欢。
    • 得到DDComponentForAndroid:一套完整有效的android組件化方案,支持組件的組件完全隔離小渊、單獨(dú)調(diào)試法褥、集成調(diào)試、組件交互酬屉、UI跳轉(zhuǎn)半等、動(dòng)態(tài)加載卸載等功能。
    • 阿里Arouter:對(duì)頁(yè)面呐萨、服務(wù)提供路由功能的中間件杀饵,簡(jiǎn)單且夠用好用,網(wǎng)上的使用介紹博客也很多谬擦,在該組件化案例中切距,我就是使用這個(gè)。
    • Router:一款單品惨远、組件化谜悟、插件化全支持的路由框架

5.2 阿里Arouter基礎(chǔ)原理

  • 這里只是說(shuō)一下基礎(chǔ)的思路
    • 在代碼里加入的@Route注解话肖,會(huì)在編譯時(shí)期通過(guò)apt生成一些存儲(chǔ)path和activityClass映射關(guān)系的類(lèi)文件,然后app進(jìn)程啟動(dòng)的時(shí)候會(huì)拿到這些類(lèi)文件葡幸,把保存這些映射關(guān)系的數(shù)據(jù)讀到內(nèi)存里(保存在map里)最筒,然后在進(jìn)行路由跳轉(zhuǎn)的時(shí)候,通過(guò)build()方法傳入要到達(dá)頁(yè)面的路由地址蔚叨。
      • 添加@Route注解然后編譯一下床蜘,就可以生成這個(gè)類(lèi),然后看一下這個(gè)類(lèi)蔑水。如下所示:
      /**
       * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
      public class ARouter$$Group$$video implements IRouteGroup {
        @Override
        public void loadInto(Map<String, RouteMeta> atlas) {
          atlas.put("/video/VideoActivity", RouteMeta.build(RouteType.ACTIVITY, VideoActivity.class, "/video/videoactivity", "video", null, -1, -2147483648));
        }
      }
      
    • ARouter會(huì)通過(guò)它自己存儲(chǔ)的路由表找到路由地址對(duì)應(yīng)的Activity.class(activity.class = map.get(path))邢锯,然后new Intent(),當(dāng)調(diào)用ARouter的withString()方法它的內(nèi)部會(huì)調(diào)用intent.putExtra(String name, String value)肤粱,調(diào)用navigation()方法弹囚,它的內(nèi)部會(huì)調(diào)用startActivity(intent)進(jìn)行跳轉(zhuǎn),這樣便可以實(shí)現(xiàn)兩個(gè)相互沒(méi)有依賴(lài)的module順利的啟動(dòng)對(duì)方的Activity了领曼。
      • 看_ARouter類(lèi)中的 _navigation方法代碼鸥鹉,在345行。
      private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
          final Context currentContext = null == context ? mContext : context;
      
          switch (postcard.getType()) {
              case ACTIVITY:
                  // Build intent
                  final Intent intent = new Intent(currentContext, postcard.getDestination());
                  intent.putExtras(postcard.getExtras());
      
                  // Set flags.
                  int flags = postcard.getFlags();
                  if (-1 != flags) {
                      intent.setFlags(flags);
                  } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                  }
      
                  // Set Actions
                  String action = postcard.getAction();
                  if (!TextUtils.isEmpty(action)) {
                      intent.setAction(action);
                  }
      
                  // Navigation in main looper.
                  runInMainThread(new Runnable() {
                      @Override
                      public void run() {
                          startActivity(requestCode, currentContext, intent, postcard, callback);
                      }
                  });
      
                  break;
              case PROVIDER:
                  //這里省略代碼
              case BOARDCAST:
              case CONTENT_PROVIDER:
              case FRAGMENT:
                  //這里省略代碼
              case METHOD:
              case SERVICE:
              default:
                  return null;
          }
          return null;
      }
      

5.3 使用Arouter注意事項(xiàng)

  • 使用阿里路由抽取工具類(lèi)庶骄,方便后期維護(hù)毁渗!
    • 首先看一下網(wǎng)絡(luò)上有一種寫(xiě)法。
      //首先通過(guò)注解添加下面代碼
      @Route(path = "/test/TestActivity")
      public class TestActivity extends BaseActivity {
      
      }
      
      //跳轉(zhuǎn)
      ARouter.getInstance().inject("/test/TestActivity");
      
    • 優(yōu)化后的寫(xiě)法
      • 下面這種做法单刁,是方便后期維護(hù)灸异。
      //存放所有的路由路徑常量
      public class ARouterConstant {
          //跳轉(zhuǎn)到視頻頁(yè)面
          public static final String ACTIVITY_VIDEO_VIDEO = "/video/VideoActivity";
          //省略部分diamagnetic
      }
      
      //存放所有的路由跳轉(zhuǎn),工具類(lèi)
      public class ARouterUtils {
          /**
           * 簡(jiǎn)單的跳轉(zhuǎn)頁(yè)面
           * @param string                string目標(biāo)界面對(duì)應(yīng)的路徑
           */
          public static void navigation(String string){
              if (string==null){
                  return;
              }
              ARouter.getInstance().build(string).navigation();
          }
      }
      
      //調(diào)用
      @Route(path = ARouterConstant.ACTIVITY_VIDEO_VIDEO)
      public class VideoActivity extends BaseActivity {
      
      }
      ARouterUtils.navigation(ARouterConstant.ACTIVITY_VIDEO_VIDEO);
      

5.4 如何獲取fragment實(shí)例

06.關(guān)于其他內(nèi)容介紹

6.1 關(guān)于博客匯總鏈接

6.1 參考博客鏈接

6.2 關(guān)于我的博客

6.3 開(kāi)源項(xiàng)目地址

組件化實(shí)踐項(xiàng)目開(kāi)源地址:https://github.com/yangchong211/LifeHelper
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肺樟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逻淌,更是在濱河造成了極大的恐慌么伯,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卡儒,死亡現(xiàn)場(chǎng)離奇詭異田柔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)骨望,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)硬爆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人擎鸠,你說(shuō)我怎么就攤上這事缀磕。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵虐骑,是天一觀的道長(zhǎng)准验。 經(jīng)常有香客問(wèn)我,道長(zhǎng)廷没,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任垂寥,我火速辦了婚禮颠黎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘滞项。我一直安慰自己狭归,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布文判。 她就那樣靜靜地躺著过椎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戏仓。 梳的紋絲不亂的頭發(fā)上疚宇,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音赏殃,去河邊找鬼敷待。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仁热,可吹牛的內(nèi)容都是我干的榜揖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抗蠢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼举哟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起迅矛,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妨猩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后诬乞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體册赛,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年震嫉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了森瘪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡票堵,死狀恐怖扼睬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤窗宇,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布措伐,位于F島的核電站,受9級(jí)特大地震影響军俊,放射性物質(zhì)發(fā)生泄漏侥加。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一粪躬、第九天 我趴在偏房一處隱蔽的房頂上張望担败。 院中可真熱鬧,春花似錦镰官、人聲如沸提前。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狈网。三九已至,卻和暖如春笨腥,著一層夾襖步出監(jiān)牢的瞬間拓哺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工扇雕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拓售,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓镶奉,卻偏偏與公主長(zhǎng)得像础淤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哨苛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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