組件化方案調(diào)研
組件化概念
組件化就是將一個(gè)app分成多個(gè)Module鼓鲁,如下圖钟哥,每個(gè)Module都是一個(gè)組件(也可以是一個(gè)基礎(chǔ)庫(kù)供組件依賴(lài))满葛,開(kāi)發(fā)的過(guò)程中我們可以單獨(dú)調(diào)試部分組件,組件間不需要互相依賴(lài)芬位,但可以相互調(diào)用龙巨,最終發(fā)布的時(shí)候所有組件以lib的形式被主app工程依賴(lài)并打包成一個(gè)apk额获。
模塊化、插件化和組件化的關(guān)系
對(duì)于這三者的關(guān)系:有不同的說(shuō)法恭应。
截取一些,以供參考耘眨,其實(shí)雖然每個(gè)人都有每個(gè)的不通過(guò)的理解和說(shuō)法昼榛,但是內(nèi)涵其實(shí)是一樣的。
模塊化與組件化
-
得到(張明慶)
在技術(shù)開(kāi)發(fā)領(lǐng)域剔难,模塊化是指分拆代碼胆屿,即當(dāng)我們的代碼特別臃腫的時(shí)候,用模塊化將代碼分而治之偶宫、解耦分層非迹。具體到 android 領(lǐng)域,模塊化的具體實(shí)施方法分為插件化和組件化纯趋。
《 Java 應(yīng)用架構(gòu)設(shè)計(jì):模塊化模式與 OSGi 》一書(shū)中對(duì)它的定義是:模塊化是一種處理復(fù)雜系統(tǒng)分解為更好的可管理模塊的方式憎兽。
-
《安居客》(張磊)組件和模塊做個(gè)區(qū)別定義
- 組件:指的是單一的功能組件,如地圖組件(MapSDK)吵冒、支付組件(AnjukePay)纯命、路由組件(Router)等等;
- 模塊:指的是獨(dú)立的業(yè)務(wù)模塊痹栖,如新房模塊(NewHouseModule)亿汞、二手房模塊(SecondHouseModule)、即時(shí)通訊模塊(InstantMessagingModule)等等揪阿;模塊相對(duì)于組件來(lái)說(shuō)粒度更大疗我。
綜上所述咆畏,模塊化與組件化都是對(duì)于一個(gè)整體功能的封裝,只不過(guò)粒度不一樣吴裤。我們本文中所指的組件化旧找,和上述3中提到的模塊化是一致的。
插件化與組件化
一套完整的插件化或組件化都必須能夠?qū)崿F(xiàn)單獨(dú)調(diào)試嚼摩、集成編譯钦讳、數(shù)據(jù)傳輸、UI 跳轉(zhuǎn)枕面、生命周期和代碼邊界這六大功能愿卒。
插件化和組件化最重要而且是唯一的區(qū)別的就是:插件化可以動(dòng)態(tài)增加和修改線上的模塊,組件化的動(dòng)態(tài)能力相對(duì)較弱潮秘,只能對(duì)線上已有模塊進(jìn)行動(dòng)態(tài)的加載和卸載琼开,不能新增和修改。
插件化讓我們的App的運(yùn)行的時(shí)候能夠動(dòng)態(tài)的進(jìn)行組裝枕荞,就像我們使用chrome的插件一樣柜候,非常的方便,甚至演變到后期躏精,插件化越來(lái)越像[虛擬機(jī)]發(fā)展渣刷,使用一個(gè)類(lèi)似[boot]的殼,就可以像Java虛擬機(jī)加載Java文件一樣加載一個(gè)App矗烛。插件化主要涉及對(duì)系統(tǒng)加載dex的依賴(lài)辅柴,所以適配起來(lái)坑比較多。
組件化可以說(shuō)和插件化有異曲同工之妙瞭吃,只不過(guò)插件化是在[運(yùn)行時(shí)]碌嘀,而組件化是在[編譯時(shí)]。換句話說(shuō)歪架,插件化是基于多APK的股冗,而組件化本質(zhì)上還是只有一個(gè)APK。編譯一個(gè)大型App的時(shí)間時(shí)間很長(zhǎng)和蚪,可能2止状,3分鐘都不夠.[組件化] 就可以很好的解決這樣的問(wèn)題,此外攒霹,由于整個(gè)App的各個(gè)業(yè)務(wù)被分離了导俘,所以它們之間的耦合度也就被降低了,各個(gè)業(yè)務(wù)線可以由專(zhuān)門(mén)的開(kāi)發(fā)同學(xué)進(jìn)行開(kāi)發(fā)剔蹋,相互之間也不會(huì)有干擾旅薄,提升開(kāi)發(fā)效率
組件化詳細(xì)方案
代碼組件之間解耦
按照業(yè)務(wù)邏輯劃分模塊,對(duì)于一般app來(lái)說(shuō),可以分為三層。
- 基礎(chǔ)庫(kù)(比如網(wǎng)絡(luò)庫(kù)少梁,數(shù)據(jù)庫(kù)洛口,utils,圖片加載庫(kù))
- 基礎(chǔ)模塊凯沪,但是沒(méi)有獨(dú)立運(yùn)行調(diào)試的必要第焰,一般只是當(dāng)作com.android.library被別的獨(dú)立模塊引用(比如登陸模塊,分享模塊妨马,視頻播放模塊)
- 業(yè)務(wù)模塊挺举,可以進(jìn)行單獨(dú)編譯,打包烘跺,測(cè)試(比如拍攝模塊湘纵,搜索模塊,視頻流模塊滤淳,小視頻模塊)
這三個(gè)層次只能向下依賴(lài)梧喷,比如3層依賴(lài)2層,2層依賴(lài)1層脖咐。每一層里面互相不能依賴(lài)铺敌。
這符合依賴(lài)倒置原則
業(yè)界模塊劃分的例子
-
安居客
4.jpg -
滴滴組件化的圖
3.jpg 微信
組件工程單獨(dú)開(kāi)發(fā)調(diào)試
先來(lái)看看Android組件化需要實(shí)現(xiàn)的目標(biāo)。(什么是組件化構(gòu)建屁擅?)
- 項(xiàng)目模塊能夠單獨(dú)啟動(dòng)測(cè)試
- 能夠根據(jù)需求引入或刪除某些業(yè)務(wù)模塊
- 通過(guò)不同模塊的組合偿凭,組成不同的App
對(duì)于第一點(diǎn):Android是通過(guò)應(yīng)用com.android.application或com.android.library來(lái)決定該模塊是以App模式還是以Library模式構(gòu)建。App模式和Library模式的最大區(qū)別就是派歌,App能夠啟動(dòng)弯囊,而Library不可以。所以如果我們的模塊能獨(dú)立啟動(dòng)的話硝皂,我們需要每次手動(dòng)去改動(dòng)模塊的build.gradle文件。好一點(diǎn)的做法定義一個(gè)布爾值來(lái)判斷是否處于debug模式作谭,但是這里有個(gè)問(wèn)題是稽物,不是每個(gè)模塊都能獨(dú)立啟動(dòng)的。所以無(wú)論采用何種方案折欠,都需要我們手動(dòng)管理贝或。
對(duì)于第二點(diǎn):當(dāng)我們開(kāi)發(fā)好業(yè)務(wù)模塊后,可能我們需要頻繁的新增或刪除某些業(yè)務(wù)模塊锐秦。如果是這樣的話咪奖,我們也是需要頻繁手動(dòng)修改App的build.gradle。
對(duì)于第三點(diǎn):有時(shí)候酱床,我們可能會(huì)在不同的App中引用相同的組件(例如:滴滴的普通版和企業(yè)版羊赵,普通版包含企業(yè)版的功能),這個(gè)時(shí)候,我們也不希望要頻繁手動(dòng)管理組件依賴(lài)昧捷,特別是在組件還可以獨(dú)立運(yùn)行的時(shí)候闲昭。
所以,在我們實(shí)踐組件化的時(shí)候靡挥,最大的問(wèn)題就是序矩,我們需要頻繁的手動(dòng)build.gradle文件來(lái)管理組件應(yīng)用的插件和App的依賴(lài)。
對(duì)于這一部分跋破,主要的就是開(kāi)發(fā)一個(gè)gradle插件簸淀,對(duì)工程進(jìn)行自動(dòng)化管理,避免人為切換導(dǎo)致的各種錯(cuò)誤毒返。
組件之間互相通信
由于組件之間完全解耦租幕,互相無(wú)法直接進(jìn)行調(diào)用,所以通過(guò)接口+實(shí)現(xiàn)的結(jié)構(gòu)進(jìn)行組件間的通信饿悬。每個(gè)組件聲明自己提供的服務(wù) Service API令蛉,這些 Service 都是一些接口,組件負(fù)責(zé)將這些 Service 實(shí)現(xiàn)并注冊(cè)到一個(gè)統(tǒng)一的路由 Router 中去狡恬,如果要使用某個(gè)組件的功能珠叔,只需要向Router 請(qǐng)求這個(gè) Service 的實(shí)現(xiàn),具體的實(shí)現(xiàn)細(xì)節(jié)我們?nèi)徊魂P(guān)心弟劲,只要能返回我們需要的結(jié)果就可以了祷安。在組件化架構(gòu)設(shè)計(jì)圖中 Common 組件就包含了路由服務(wù)組件,里面包括了每個(gè)組件的路由入口和跳轉(zhuǎn)兔乞。
由于各個(gè)模塊是嚴(yán)格劃分解耦的汇鞭,各個(gè)模塊對(duì)與彼此的存在是完全不知道的,所以各個(gè)組件的互相通信就需要一個(gè)通用的協(xié)議來(lái)進(jìn)行庸追。于是引入了路由框架霍骄。
通過(guò)上圖可以看到,我們?cè)谧罨A(chǔ)的Common庫(kù)中淡溯,創(chuàng)建了一個(gè)路由Router读整,中間有n個(gè)模塊Module,這個(gè)Module實(shí)際上就是Android Studio中的module咱娶,這些Module都是Android Library Module米间,最上面的Module Main是可運(yùn)行的Android Application Module。
這幾個(gè)Module都引用了Common庫(kù)膘侮,同時(shí)Main Module還引用了A屈糊、B、N這幾個(gè)Module琼了,經(jīng)過(guò)這樣的處理之后逻锐,所有的Module之間的相互調(diào)用就都消失了,耦合性降低,所有的通信統(tǒng)一都交給Router來(lái)處理分發(fā)谦去,而注冊(cè)工作則交由Main Module去進(jìn)行初始化慷丽。這個(gè)架構(gòu)思想其實(shí)和Binder的思想很類(lèi)似,采用C/S模式鳄哭,模塊之間隔離要糊,數(shù)據(jù)通過(guò)共享區(qū)域進(jìn)行傳遞。模塊與模塊之間只暴露對(duì)外開(kāi)放的Action妆丘,所以也具備面向接口編程思想锄俄。
圖中的紅色矩形代表的是行動(dòng)Action,Action是具體的執(zhí)行類(lèi)勺拣,其內(nèi)部的invoke方法是具體執(zhí)行的代碼邏輯奶赠。如果涉及到并發(fā)操作的話,可以在invoke方法內(nèi)加入鎖药有,或者直接在invoke方法上加上synchronized描述毅戈。
圖中的黃色矩形代表的是供應(yīng)商Provider,每個(gè)Provider中包含1個(gè)或多個(gè)Action愤惰,其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)以HashMap來(lái)存儲(chǔ)Action苇经。首先HashMap查詢(xún)的時(shí)間復(fù)雜度是O(1),符合我們對(duì)調(diào)用速度上的要求宦言,其次扇单,由于我們是統(tǒng)一進(jìn)行注冊(cè),所以在寫(xiě)入時(shí)并不存在并發(fā)線程并發(fā)問(wèn)題奠旺,在讀取時(shí)蜘澜,并發(fā)問(wèn)題則交由Action的invoke去具體處理。在每一個(gè)Module內(nèi)都會(huì)有1個(gè)或多個(gè)供應(yīng)商Provider(如果不包含Provider响疚,那么這個(gè)Module將無(wú)法為其他Module提供服務(wù))鄙信。
途中藍(lán)色矩形代表的是路由Router,每個(gè)Router中包含多個(gè)Provider忿晕,其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)也是以HashMap來(lái)存儲(chǔ)Provider装诡,原理也和Provider是一樣的。之所以用了兩次HashMap杏糙,有兩點(diǎn)原因慎王,一個(gè)是因?yàn)檫@樣做蚓土,不容易導(dǎo)致Action的重名宏侍,另一個(gè)是因?yàn)樵谧?cè)的時(shí)候,只注冊(cè)Provider會(huì)減少注冊(cè)代碼蜀漆,更易讀谅河。并且由于HashMap的查詢(xún)時(shí)間復(fù)雜度是O(1),所以?xún)纱尾檎也粫?huì)浪費(fèi)太多時(shí)間。當(dāng)查找不到對(duì)應(yīng)Action的時(shí)候绷耍,Router會(huì)生成一個(gè)ErrorAction吐限,會(huì)告之調(diào)用者沒(méi)有找到對(duì)應(yīng)的Action,由調(diào)用者來(lái)決定接下來(lái)如何處理褂始。
一次請(qǐng)求流程
通過(guò)Router調(diào)用的具體流程是這樣的:
任意代碼創(chuàng)建一個(gè)RouterRequest诸典,包含Provider和Action信息,向Router進(jìn)行請(qǐng)求崎苗。
Router接到請(qǐng)求狐粱,通過(guò)RouterRequest的Provider信息,在內(nèi)部的HashMap中查找對(duì)應(yīng)的Provider胆数。
Provider接到請(qǐng)求肌蜻,在內(nèi)部的HashMap中查找到對(duì)應(yīng)的Action信息。
Action調(diào)用invoke方法必尼。
返回invoke方法生成的ActionResult蒋搜。
將Result封裝成RouterResponse,返回給調(diào)用者判莉。
上述描述豆挽,引用:Android架構(gòu)思考
這只是一個(gè)原理性的描述,不同的開(kāi)源框架具體實(shí)現(xiàn)細(xì)節(jié)可能不同骂租,想了解具體實(shí)現(xiàn)細(xì)節(jié)祷杈,可以研究一下開(kāi)源路由代碼。
開(kāi)源框架實(shí)現(xiàn):
- WMRouter(美團(tuán)方案)
- ARouter(阿里方案)
- Andromeda(支持跨進(jìn)程)
- DDComponentForAndroid(得到方案)
- ModularizationArchitecture(支持跨進(jìn)程)
UI 跳轉(zhuǎn)問(wèn)題
可以說(shuō) UI 跳轉(zhuǎn)也是組件間通信的一種渗饮,但是屬于比較特殊的數(shù)據(jù)傳遞但汞。不過(guò)一般 UI 跳轉(zhuǎn)基本都會(huì)單獨(dú)處理,一般通過(guò)短鏈的方式來(lái)跳轉(zhuǎn)到具體的 Activity互站。每個(gè)組件可以注冊(cè)自己所能處理的短鏈的 Scheme 和 Host私蕾,并定義傳輸數(shù)據(jù)的格式,然后注冊(cè)到統(tǒng)一的 Router 中胡桃,Router 通過(guò) Scheme 和 Host 的匹配關(guān)系負(fù)責(zé)分發(fā)路由踩叭。但目前比較主流的做法是通過(guò)在每個(gè) Activity 上添加注解,然后通過(guò) APT 形成具體的邏輯代碼翠胰。
目前開(kāi)源的框架有:
- ARouter 框架容贝,通過(guò)注解方式進(jìn)行頁(yè)面跳轉(zhuǎn),阿里出品
- ActivityRouter
- WMRouter 美團(tuán)開(kāi)源框架
- DeepLinkDispatch
資源沖突
- 對(duì)于多個(gè) Bussines Module 中資源名沖突的問(wèn)題之景,可以通過(guò)在 build.gradle 定義前綴的方式解決:
defaultConfig {
...
resourcePrefix "new_house_"
...
}
- 多個(gè)mainfest問(wèn)題
每個(gè)可以獨(dú)立運(yùn)行的組件斤富,都需要自己獨(dú)立的mainfest,根據(jù)組件是否是獨(dú)立運(yùn)行锻狗,配置不同的Manifest路徑
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
組件加載
- 多個(gè)application的問(wèn)題
可以嘗試用兩種方式字節(jié)碼插入模式是在dex生成之前满力,掃描所有的ApplicationLike類(lèi)(其有一個(gè)共同的父類(lèi))焕参,然后通過(guò)javassist在主項(xiàng)目的Application.onCreate()中插入調(diào)用ApplicationLike.onCreate()的代碼。這樣就相當(dāng)于每個(gè)組件在application啟動(dòng)的時(shí)候就加載起來(lái)了油额。
反射調(diào)用的方式是手動(dòng)在Application.onCreate()中或者在其他合適的時(shí)機(jī)手動(dòng)通過(guò)反射的方式來(lái)調(diào)用ApplicationLike.onCreate()叠纷。之所以提供這種方式原因有兩個(gè):對(duì)代碼進(jìn)行掃描和插入會(huì)增加編譯的時(shí)間,特別在debug的時(shí)候會(huì)影響效率潦嘶,并且這種模式對(duì)Instant Run支持不好涩嚣;另一個(gè)原因是可以更靈活的控制加載或者卸載時(shí)機(jī)。
對(duì)于1掂僵,2方案缓艳,具體可以查看得到(DDComponentForAndroid)方案中的實(shí)現(xiàn)。
業(yè)內(nèi)一些組件化開(kāi)源方案
- 得到方案(DDComponentForAndroid)
- CC(ComponentCaller)看峻,漸進(jìn)式組件方案
- ModularizationArchitecture
- Atlas(阿里方案)
可行性(組件化與****項(xiàng)目如何結(jié)合)
綜上所述阶淘,組件化是一個(gè)系統(tǒng)性工程,需要從編譯互妓,代碼結(jié)構(gòu)溪窒,通信框架,打包冯勉,測(cè)試等各個(gè)環(huán)節(jié)進(jìn)行重構(gòu)澈蚌。
對(duì)于已有的app來(lái)說(shuō),采用漸進(jìn)式的組件化是比較穩(wěn)妥地方式灼狰。
下面針對(duì)****進(jìn)行一些分析宛瞄,將****中的module庫(kù)進(jìn)行一個(gè)劃分
-
基礎(chǔ)庫(kù)
baseAppInfoLib common-lib-net
-
功能庫(kù)
sohuUpload downloadsdk sohuMediaPlayerSdk sohuMediaPlayerLib sohuDanmuLib scaleview sohuVideoEditor sohuPrivilegeLib
-
組件庫(kù)
qianliyanlib sohuVideoMobile fivesixapp
短期規(guī)劃
目前拍攝模塊(qianliyanlib)相對(duì)獨(dú)立,先從拍攝模塊進(jìn)行解耦
首先與拍攝模塊有直接耦合關(guān)系的是上傳視頻組件交胚,上傳視頻組件中份汗,耦合了一部分視頻轉(zhuǎn)碼的狀態(tài)展示。
可以考慮先將拍攝模塊與上傳模塊先行進(jìn)行組件化蝴簇。兩個(gè)模塊中的狀態(tài)互相依賴(lài)杯活,則通過(guò)注冊(cè)各自模塊提供的service進(jìn)行接口編程方式實(shí)現(xiàn)。
后期如果有新的需求熬词,并且是比較大的功能模塊旁钧,那么就直接用組件模式進(jìn)行開(kāi)發(fā)。
通過(guò)這個(gè)組件化后互拾,整個(gè)拍攝模塊與上傳模塊歪今,就可以單獨(dú)調(diào)試與運(yùn)行,在解耦過(guò)程中颜矿,逐步探索組件化的一些細(xì)節(jié)寄猩。為后期整體app組件化積累經(jīng)驗(yàn)。
后期規(guī)劃
可以按照功能模塊進(jìn)行劃分如下:
- 紅包模塊
- 搜索模塊
- 商場(chǎng)訂單模塊
- 首頁(yè)框架模塊
- 小視頻播放模塊(包含列表與詳情)
- 視頻信息流模塊(包含首頁(yè)tab或衡,熱點(diǎn)tab焦影,訂閱流,包含流播放封断,視頻詳情中播放)
- 個(gè)人中心模塊
參考資料
http://blog.spinytech.com/2016/12/28/android_modularization/
https://github.com/luckybilly/AndroidComponentizeLibs
https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w
http://blog.zhaiyifan.cn/2016/10/20/android-new-project-from-0-p11/
https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w
http://www.trinea.cn/android/didi-internationalization-android-evolution/