組件化方案調(diào)研
組件化概念
組件化就是將一個app分成多個Module,如下圖,每個Module都是一個組件(也可以是一個基礎(chǔ)庫供組件依賴),開發(fā)的過程中我們可以單獨調(diào)試部分組件,組件間不需要互相依賴,但可以相互調(diào)用,最終發(fā)布的時候所有組件以lib的形式被主app工程依賴并打包成一個apk。

模塊化、插件化和組件化的關(guān)系
對于這三者的關(guān)系:有不同的說法。
截取一些,以供參考,其實雖然每個人都有每個的不通過的理解和說法,但是內(nèi)涵其實是一樣的。
模塊化與組件化
-
得到(張明慶)
在技術(shù)開發(fā)領(lǐng)域,模塊化是指分拆代碼,即當(dāng)我們的代碼特別臃腫的時候,用模塊化將代碼分而治之、解耦分層。具體到 android 領(lǐng)域,模塊化的具體實施方法分為插件化和組件化。
《 Java 應(yīng)用架構(gòu)設(shè)計:模塊化模式與 OSGi 》一書中對它的定義是:模塊化是一種處理復(fù)雜系統(tǒng)分解為更好的可管理模塊的方式。
-
《安居客》(張磊)組件和模塊做個區(qū)別定義
- 組件:指的是單一的功能組件,如地圖組件(MapSDK)、支付組件(AnjukePay)、路由組件(Router)等等;
- 模塊:指的是獨立的業(yè)務(wù)模塊,如新房模塊(NewHouseModule)、二手房模塊(SecondHouseModule)、即時通訊模塊(InstantMessagingModule)等等;模塊相對于組件來說粒度更大。
綜上所述,模塊化與組件化都是對于一個整體功能的封裝,只不過粒度不一樣。我們本文中所指的組件化,和上述3中提到的模塊化是一致的。
插件化與組件化
一套完整的插件化或組件化都必須能夠?qū)崿F(xiàn)單獨調(diào)試、集成編譯、數(shù)據(jù)傳輸、UI 跳轉(zhuǎn)、生命周期和代碼邊界這六大功能。
插件化和組件化最重要而且是唯一的區(qū)別的就是:插件化可以動態(tài)增加和修改線上的模塊,組件化的動態(tài)能力相對較弱,只能對線上已有模塊進(jìn)行動態(tài)的加載和卸載,不能新增和修改。
插件化讓我們的App的運行的時候能夠動態(tài)的進(jìn)行組裝,就像我們使用chrome的插件一樣,非常的方便,甚至演變到后期,插件化越來越像[虛擬機]發(fā)展,使用一個類似[boot]的殼,就可以像Java虛擬機加載Java文件一樣加載一個App。插件化主要涉及對系統(tǒng)加載dex的依賴,所以適配起來坑比較多。
組件化可以說和插件化有異曲同工之妙,只不過插件化是在[運行時],而組件化是在[編譯時]。換句話說,插件化是基于多APK的,而組件化本質(zhì)上還是只有一個APK。編譯一個大型App的時間時間很長,可能2,3分鐘都不夠.[組件化] 就可以很好的解決這樣的問題,此外,由于整個App的各個業(yè)務(wù)被分離了,所以它們之間的耦合度也就被降低了,各個業(yè)務(wù)線可以由專門的開發(fā)同學(xué)進(jìn)行開發(fā),相互之間也不會有干擾,提升開發(fā)效率
組件化詳細(xì)方案
代碼組件之間解耦
按照業(yè)務(wù)邏輯劃分模塊,對于一般app來說,可以分為三層。
- 基礎(chǔ)庫(比如網(wǎng)絡(luò)庫,數(shù)據(jù)庫,utils,圖片加載庫)
- 基礎(chǔ)模塊,但是沒有獨立運行調(diào)試的必要,一般只是當(dāng)作com.android.library被別的獨立模塊引用(比如登陸模塊,分享模塊,視頻播放模塊)
- 業(yè)務(wù)模塊,可以進(jìn)行單獨編譯,打包,測試(比如拍攝模塊,搜索模塊,視頻流模塊,小視頻模塊)
這三個層次只能向下依賴,比如3層依賴2層,2層依賴1層。每一層里面互相不能依賴。
這符合依賴倒置原則
業(yè)界模塊劃分的例子
-
安居客
4.jpg -
滴滴組件化的圖
3.jpg 微信

組件工程單獨開發(fā)調(diào)試
先來看看Android組件化需要實現(xiàn)的目標(biāo)。(什么是組件化構(gòu)建?)
- 項目模塊能夠單獨啟動測試
- 能夠根據(jù)需求引入或刪除某些業(yè)務(wù)模塊
- 通過不同模塊的組合,組成不同的App
對于第一點:Android是通過應(yīng)用com.android.application或com.android.library來決定該模塊是以App模式還是以Library模式構(gòu)建。App模式和Library模式的最大區(qū)別就是,App能夠啟動,而Library不可以。所以如果我們的模塊能獨立啟動的話,我們需要每次手動去改動模塊的build.gradle文件。好一點的做法定義一個布爾值來判斷是否處于debug模式,但是這里有個問題是,不是每個模塊都能獨立啟動的。所以無論采用何種方案,都需要我們手動管理。
對于第二點:當(dāng)我們開發(fā)好業(yè)務(wù)模塊后,可能我們需要頻繁的新增或刪除某些業(yè)務(wù)模塊。如果是這樣的話,我們也是需要頻繁手動修改App的build.gradle。
對于第三點:有時候,我們可能會在不同的App中引用相同的組件(例如:滴滴的普通版和企業(yè)版,普通版包含企業(yè)版的功能),這個時候,我們也不希望要頻繁手動管理組件依賴,特別是在組件還可以獨立運行的時候。
所以,在我們實踐組件化的時候,最大的問題就是,我們需要頻繁的手動build.gradle文件來管理組件應(yīng)用的插件和App的依賴。
對于這一部分,主要的就是開發(fā)一個gradle插件,對工程進(jìn)行自動化管理,避免人為切換導(dǎo)致的各種錯誤。
組件之間互相通信
由于組件之間完全解耦,互相無法直接進(jìn)行調(diào)用,所以通過接口+實現(xiàn)的結(jié)構(gòu)進(jìn)行組件間的通信。每個組件聲明自己提供的服務(wù) Service API,這些 Service 都是一些接口,組件負(fù)責(zé)將這些 Service 實現(xiàn)并注冊到一個統(tǒng)一的路由 Router 中去,如果要使用某個組件的功能,只需要向Router 請求這個 Service 的實現(xiàn),具體的實現(xiàn)細(xì)節(jié)我們?nèi)徊魂P(guān)心,只要能返回我們需要的結(jié)果就可以了。在組件化架構(gòu)設(shè)計圖中 Common 組件就包含了路由服務(wù)組件,里面包括了每個組件的路由入口和跳轉(zhuǎn)。
由于各個模塊是嚴(yán)格劃分解耦的,各個模塊對與彼此的存在是完全不知道的,所以各個組件的互相通信就需要一個通用的協(xié)議來進(jìn)行。于是引入了路由框架。

通過上圖可以看到,我們在最基礎(chǔ)的Common庫中,創(chuàng)建了一個路由Router,中間有n個模塊Module,這個Module實際上就是Android Studio中的module,這些Module都是Android Library Module,最上面的Module Main是可運行的Android Application Module。
這幾個Module都引用了Common庫,同時Main Module還引用了A、B、N這幾個Module,經(jīng)過這樣的處理之后,所有的Module之間的相互調(diào)用就都消失了,耦合性降低,所有的通信統(tǒng)一都交給Router來處理分發(fā),而注冊工作則交由Main Module去進(jìn)行初始化。這個架構(gòu)思想其實和Binder的思想很類似,采用C/S模式,模塊之間隔離,數(shù)據(jù)通過共享區(qū)域進(jìn)行傳遞。模塊與模塊之間只暴露對外開放的Action,所以也具備面向接口編程思想。
圖中的紅色矩形代表的是行動Action,Action是具體的執(zhí)行類,其內(nèi)部的invoke方法是具體執(zhí)行的代碼邏輯。如果涉及到并發(fā)操作的話,可以在invoke方法內(nèi)加入鎖,或者直接在invoke方法上加上synchronized描述。
圖中的黃色矩形代表的是供應(yīng)商Provider,每個Provider中包含1個或多個Action,其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)以HashMap來存儲Action。首先HashMap查詢的時間復(fù)雜度是O(1),符合我們對調(diào)用速度上的要求,其次,由于我們是統(tǒng)一進(jìn)行注冊,所以在寫入時并不存在并發(fā)線程并發(fā)問題,在讀取時,并發(fā)問題則交由Action的invoke去具體處理。在每一個Module內(nèi)都會有1個或多個供應(yīng)商Provider(如果不包含Provider,那么這個Module將無法為其他Module提供服務(wù))。
途中藍(lán)色矩形代表的是路由Router,每個Router中包含多個Provider,其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)也是以HashMap來存儲Provider,原理也和Provider是一樣的。之所以用了兩次HashMap,有兩點原因,一個是因為這樣做,不容易導(dǎo)致Action的重名,另一個是因為在注冊的時候,只注冊Provider會減少注冊代碼,更易讀。并且由于HashMap的查詢時間復(fù)雜度是O(1),所以兩次查找不會浪費太多時間。當(dāng)查找不到對應(yīng)Action的時候,Router會生成一個ErrorAction,會告之調(diào)用者沒有找到對應(yīng)的Action,由調(diào)用者來決定接下來如何處理。
一次請求流程
通過Router調(diào)用的具體流程是這樣的:

任意代碼創(chuàng)建一個RouterRequest,包含Provider和Action信息,向Router進(jìn)行請求。
Router接到請求,通過RouterRequest的Provider信息,在內(nèi)部的HashMap中查找對應(yīng)的Provider。
Provider接到請求,在內(nèi)部的HashMap中查找到對應(yīng)的Action信息。
Action調(diào)用invoke方法。
返回invoke方法生成的ActionResult。
將Result封裝成RouterResponse,返回給調(diào)用者。
上述描述,引用:Android架構(gòu)思考
這只是一個原理性的描述,不同的開源框架具體實現(xiàn)細(xì)節(jié)可能不同,想了解具體實現(xiàn)細(xì)節(jié),可以研究一下開源路由代碼。
開源框架實現(xiàn):
- WMRouter(美團方案)
- ARouter(阿里方案)
- Andromeda(支持跨進(jìn)程)
- DDComponentForAndroid(得到方案)
- ModularizationArchitecture(支持跨進(jìn)程)
UI 跳轉(zhuǎn)問題
可以說 UI 跳轉(zhuǎn)也是組件間通信的一種,但是屬于比較特殊的數(shù)據(jù)傳遞。不過一般 UI 跳轉(zhuǎn)基本都會單獨處理,一般通過短鏈的方式來跳轉(zhuǎn)到具體的 Activity。每個組件可以注冊自己所能處理的短鏈的 Scheme 和 Host,并定義傳輸數(shù)據(jù)的格式,然后注冊到統(tǒng)一的 Router 中,Router 通過 Scheme 和 Host 的匹配關(guān)系負(fù)責(zé)分發(fā)路由。但目前比較主流的做法是通過在每個 Activity 上添加注解,然后通過 APT 形成具體的邏輯代碼。
目前開源的框架有:
- ARouter 框架,通過注解方式進(jìn)行頁面跳轉(zhuǎn),阿里出品
- ActivityRouter
- WMRouter 美團開源框架
- DeepLinkDispatch
資源沖突
- 對于多個 Bussines Module 中資源名沖突的問題,可以通過在 build.gradle 定義前綴的方式解決:
defaultConfig {
...
resourcePrefix "new_house_"
...
}
- 多個mainfest問題
每個可以獨立運行的組件,都需要自己獨立的mainfest,根據(jù)組件是否是獨立運行,配置不同的Manifest路徑
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
組件加載
- 多個application的問題
可以嘗試用兩種方式字節(jié)碼插入模式是在dex生成之前,掃描所有的ApplicationLike類(其有一個共同的父類),然后通過javassist在主項目的Application.onCreate()中插入調(diào)用ApplicationLike.onCreate()的代碼。這樣就相當(dāng)于每個組件在application啟動的時候就加載起來了。
反射調(diào)用的方式是手動在Application.onCreate()中或者在其他合適的時機手動通過反射的方式來調(diào)用ApplicationLike.onCreate()。之所以提供這種方式原因有兩個:對代碼進(jìn)行掃描和插入會增加編譯的時間,特別在debug的時候會影響效率,并且這種模式對Instant Run支持不好;另一個原因是可以更靈活的控制加載或者卸載時機。
對于1,2方案,具體可以查看得到(DDComponentForAndroid)方案中的實現(xiàn)。
業(yè)內(nèi)一些組件化開源方案
- 得到方案(DDComponentForAndroid)
- CC(ComponentCaller),漸進(jìn)式組件方案
- ModularizationArchitecture
- Atlas(阿里方案)
可行性(組件化與****項目如何結(jié)合)
綜上所述,組件化是一個系統(tǒng)性工程,需要從編譯,代碼結(jié)構(gòu),通信框架,打包,測試等各個環(huán)節(jié)進(jìn)行重構(gòu)。
對于已有的app來說,采用漸進(jìn)式的組件化是比較穩(wěn)妥地方式。
下面針對****進(jìn)行一些分析,將****中的module庫進(jìn)行一個劃分
-
基礎(chǔ)庫
baseAppInfoLib common-lib-net -
功能庫
sohuUpload downloadsdk sohuMediaPlayerSdk sohuMediaPlayerLib sohuDanmuLib scaleview sohuVideoEditor sohuPrivilegeLib -
組件庫
qianliyanlib sohuVideoMobile fivesixapp
短期規(guī)劃
目前拍攝模塊(qianliyanlib)相對獨立,先從拍攝模塊進(jìn)行解耦
首先與拍攝模塊有直接耦合關(guān)系的是上傳視頻組件,上傳視頻組件中,耦合了一部分視頻轉(zhuǎn)碼的狀態(tài)展示。
可以考慮先將拍攝模塊與上傳模塊先行進(jìn)行組件化。兩個模塊中的狀態(tài)互相依賴,則通過注冊各自模塊提供的service進(jìn)行接口編程方式實現(xiàn)。
后期如果有新的需求,并且是比較大的功能模塊,那么就直接用組件模式進(jìn)行開發(fā)。
通過這個組件化后,整個拍攝模塊與上傳模塊,就可以單獨調(diào)試與運行,在解耦過程中,逐步探索組件化的一些細(xì)節(jié)。為后期整體app組件化積累經(jīng)驗。
后期規(guī)劃
可以按照功能模塊進(jìn)行劃分如下:
- 紅包模塊
- 搜索模塊
- 商場訂單模塊
- 首頁框架模塊
- 小視頻播放模塊(包含列表與詳情)
- 視頻信息流模塊(包含首頁tab,熱點tab,訂閱流,包含流播放,視頻詳情中播放)
- 個人中心模塊
參考資料
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/

