組件化方案調(diào)研

組件化方案調(diào)研

組件化概念

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

1.png

模塊化、插件化和組件化的關(guān)系

對于這三者的關(guān)系:有不同的說法。
截取一些,以供參考,其實雖然每個人都有每個的不通過的理解和說法,但是內(nèi)涵其實是一樣的。

模塊化與組件化

  1. 得到(張明慶)

    在技術(shù)開發(fā)領(lǐng)域,模塊化是指分拆代碼,即當(dāng)我們的代碼特別臃腫的時候,用模塊化將代碼分而治之、解耦分層。具體到 android 領(lǐng)域,模塊化的具體實施方法分為插件化和組件化。

  2. 《 Java 應(yīng)用架構(gòu)設(shè)計:模塊化模式與 OSGi 》一書中對它的定義是:模塊化是一種處理復(fù)雜系統(tǒng)分解為更好的可管理模塊的方式。

  3. 《安居客》(張磊)組件和模塊做個區(qū)別定義

    • 組件:指的是單一的功能組件,如地圖組件(MapSDK)、支付組件(AnjukePay)、路由組件(Router)等等;
    • 模塊:指的是獨立的業(yè)務(wù)模塊,如新房模塊(NewHouseModule)、二手房模塊(SecondHouseModule)、即時通訊模塊(InstantMessagingModule)等等;模塊相對于組件來說粒度更大。

綜上所述,模塊化與組件化都是對于一個整體功能的封裝,只不過粒度不一樣。我們本文中所指的組件化,和上述3中提到的模塊化是一致的。

插件化與組件化

  1. 一套完整的插件化或組件化都必須能夠?qū)崿F(xiàn)單獨調(diào)試、集成編譯、數(shù)據(jù)傳輸、UI 跳轉(zhuǎn)、生命周期和代碼邊界這六大功能。

  2. 插件化和組件化最重要而且是唯一的區(qū)別的就是:插件化可以動態(tài)增加和修改線上的模塊,組件化的動態(tài)能力相對較弱,只能對線上已有模塊進(jìn)行動態(tài)的加載和卸載,不能新增和修改。

  3. 插件化讓我們的App的運行的時候能夠動態(tài)的進(jìn)行組裝,就像我們使用chrome的插件一樣,非常的方便,甚至演變到后期,插件化越來越像[虛擬機]發(fā)展,使用一個類似[boot]的殼,就可以像Java虛擬機加載Java文件一樣加載一個App。插件化主要涉及對系統(tǒng)加載dex的依賴,所以適配起來坑比較多。

  4. 組件化可以說和插件化有異曲同工之妙,只不過插件化是在[運行時],而組件化是在[編譯時]。換句話說,插件化是基于多APK的,而組件化本質(zhì)上還是只有一個APK。編譯一個大型App的時間時間很長,可能2,3分鐘都不夠.[組件化] 就可以很好的解決這樣的問題,此外,由于整個App的各個業(yè)務(wù)被分離了,所以它們之間的耦合度也就被降低了,各個業(yè)務(wù)線可以由專門的開發(fā)同學(xué)進(jìn)行開發(fā),相互之間也不會有干擾,提升開發(fā)效率

組件化詳細(xì)方案

代碼組件之間解耦

按照業(yè)務(wù)邏輯劃分模塊,對于一般app來說,可以分為三層。

  1. 基礎(chǔ)庫(比如網(wǎng)絡(luò)庫,數(shù)據(jù)庫,utils,圖片加載庫)
  2. 基礎(chǔ)模塊,但是沒有獨立運行調(diào)試的必要,一般只是當(dāng)作com.android.library被別的獨立模塊引用(比如登陸模塊,分享模塊,視頻播放模塊)
  3. 業(yè)務(wù)模塊,可以進(jìn)行單獨編譯,打包,測試(比如拍攝模塊,搜索模塊,視頻流模塊,小視頻模塊)

這三個層次只能向下依賴,比如3層依賴2層,2層依賴1層。每一層里面互相不能依賴。
這符合依賴倒置原則

業(yè)界模塊劃分的例子

  • 安居客


    4.jpg
  • 滴滴組件化的圖


    3.jpg
  • 微信

6.png

組件工程單獨開發(fā)調(diào)試

先來看看Android組件化需要實現(xiàn)的目標(biāo)。(什么是組件化構(gòu)建?)

  1. 項目模塊能夠單獨啟動測試
  2. 能夠根據(jù)需求引入或刪除某些業(yè)務(wù)模塊
  3. 通過不同模塊的組合,組成不同的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)行。于是引入了路由框架。

8.png

通過上圖可以看到,我們在最基礎(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)用的具體流程是這樣的:

9.png

任意代碼創(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)行劃分如下:

  1. 紅包模塊
  2. 搜索模塊
  3. 商場訂單模塊
  4. 首頁框架模塊
  5. 小視頻播放模塊(包含列表與詳情)
  6. 視頻信息流模塊(包含首頁tab,熱點tab,訂閱流,包含流播放,視頻詳情中播放)
  7. 個人中心模塊

參考資料

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://zjutkz.net/2016/10/07/%E5%85%B3%E4%BA%8EAndroid%E4%B8%9A%E5%8A%A1%E7%BB%84%E4%BB%B6%E5%8C%96%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%9D%E8%80%83/

http://www.trinea.cn/android/didi-internationalization-android-evolution/

http://tangpj.com/2018/07/22/calces-componentization/

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

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,118評論 2 59
  • iOS組件化方案探索 一、什么是組件化? 1、什么是組件? "組件"一般來說用于命名比較小的功能塊,如:下拉刷新組...
    yehot閱讀 16,327評論 14 207
  • 從小到大,我認(rèn)識很多人,也有很多朋友,有高的矮的胖的瘦的,有男的女的有文化的和文化水平不太高的,有活潑的有喜歡安靜...
    田野虎閱讀 423評論 0 3
  • 題目寫的是2018年剛開始的4個小時,但其實我真正要寫的應(yīng)該是從2017年最后一天的下午7點到2018年剛開始的第...
    CY_M閱讀 157評論 0 1

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