上篇文章 Android 組件化 之 如何優雅的實作同級組件的通信 講到
寫代碼這么多年,一個重要感受是「不要過度封裝!」
不僅僅是說業務組件不多,沒必要用這么復雜的組件化方案,
我甚至覺得組件化都不是必須的,
組件化的3條好處
- 一個工程(project)里面需要選擇依賴哪幾個組件,然后打成不同的包,
- 由第一條引申,如果有需求「每個組件可以單獨打個包來測驗」,
- 加快了增量編譯的速度,
第一條「打成不同的包」是很廣義的,除了依賴這一個工程打出不同包,還有比如有一些base組件,能自己專案里用,還能打出一個獨立的aar供其他專案用,也算作打成不同的包,這些能抽離出來的base代碼就可以作為一個組件,
組件化的犧牲
1. 如果分包能解決,為什么要犧牲便利性,抽離出組件來,
很多文章說組件化可以 「解耦」、「業務分層」、「業務隔離」、「代碼變的更好維護」,這些其實都是可以通過分包(Package)解決的,
約定好 A小姐 負責 com.xxx.packageA 下的代碼,B小姐 負責 com.xxx.packageB 的代碼,甚至可以寫個腳本,git clone 下載下來工程后,立刻執行 chmod -r 刪去某些路徑的寫權限,避免無意修改別人負責的代碼區域,
抽離出組件后并不是一蹴而就的事情,要一直投入精力保持組件成功運行也是需要不少成本的,
比如說我們的資源檔案,一般會要求加模塊名字的前綴,避免組件化專案運行程序中資源沖突,(誰讓resourcePrefix它只管檢驗不幫忙動手啊)
實戰中某個業務module里的UI或是什么代碼,需要變成通用的,供多個業務模塊使用,要下沉到 lib_base 模塊了,這就需要轉移各種資源到 lib_base,這就是要逐個檔案、逐個資源重命名,還要牽扯到所有用到這個資源的代碼,改一個資源名就可能是10+個甚至幾十+個檔案的變動,IDE是很智能的都幫忙改了,但是自己提交代碼前檢查 & 別人code review審查平白無故又要多N個檔案的作業量,這種痛苦我深有體會,
2. 關于組件獨立運行
有一些專案改造成組件化,實作的效果是「每個業務組件 + App殼組件都可以單獨打出一個App」,可以方便地單獨測驗某個業務,設想跟寫單元測驗代碼很相似,
然而效果是測驗某個業務內容后,集成到主app里能保證不出問題?不能,這時候又要跑一遍集成后的測驗流程,后面甚至發現單獨測驗業務模塊并不能省時間,干脆省略了,久而久之組件獨立運行成為了擺設,
3. 關于組件獨立運行
說組件化可以加快增量編譯速度,其實作在Android studio的instant run和越來越優化成熟的編譯機制,甚至買部好點貴點的電腦,都可以解決這個問題了,而且如果組件化維護不當,到處都是 api 引入組件,那編譯速度反而有可能延長,
場景案例
寫了這么多組件化的壞話,我也不是說組件化一無是處,
而是我覺得,判斷需不需要組件化的唯一一條要素是,當你需要使用這個工程打出不同的app的時候,才需要組件化,
這里我舉一個組件化完美適用的場景,

這是一款小說閱讀類App,最下面 lib_base 基礎組件,有3個平級的業務組件 lib_user(用戶)、lib_read(閱讀)、lib_ad(廣告),上面有3個可以打成不同 App 的組件,
- 國內版本:用戶、閱讀、廣告都用到,所以三者都參考到,打出一個國內App,
- 國外版本:不允許有廣告啊,會被下架啊,排除掉廣告組件,參考用戶和閱讀組件,打出一個國外版本App,
- 為了推廣,為了上架谷歌市場單本作品付費閱讀,甚至可以沒有用戶模塊,作品資源直接集成到apk里,1美元你直接買斷這部作品了,無需聯網無需登錄離線即可閱讀,只引入閱讀組件,打出一個單部作品的App,
從這個案例可以看到,核心原則是「是否需要依賴不同的業務層組件,打成不同的包」,
以后再新增的功能,比如說 「社交」、「購物」,應不應該獨立成組件,都應該按照這個原則來,是不是有的App需要這個組件,有的App不需要這個組件,
比如說 「登錄功能」,需要獨立封裝成一個 登錄組件 嗎?
如果沒有要打包一個沒有登錄功能的App,不需要設計單獨的 「登錄組件」,
如果你的專案 登錄 是 必須 的功能,否則后續頁面都打不開,就更不應該設計單獨的 登錄組件了,這樣的登錄功能,應該放 lib_base 里,屬于公共能力的一部分,不應該跟業務組件平級,
現象與問題
然后講講最近正在進行的一個組件化重構的方案,App有3個重要組件,「搶單」、「做單」、「我的」,跟小說閱讀類的App的「書城」、「書架」、「我的」很像,所以我拿這個小說閱讀類App當案例繼續說了,

重構前有這幾個模塊
- 書城:用于找書,
- 書架:自己收藏的,最近閱讀的書,最重要的閱讀功能也放到這個組件里,
- 我的:個人頁面,
這樣的專案結構看起來還是很清晰明了的,但是運行時間長了會發現有一個問題,
書城和書架,這兩個組件很特殊,有很多功能都是重合的,比如說對于作品的介紹頁,作品章節串列頁面,這些功能因為共用,都慢慢轉移到了 lib_base 里面,導致 lib_base 越來越臃腫,
并且這種功能只用于書城和書架,并不能用在我的組件里,如果以后要添加任務和社區組件,也不會用到這些頁面的功能,
解決
lib_base 越來越臃腫了怎么辦?
按照我上篇文章 2.3 ARouter的服務管理 (更合適的介面定義) 講的——定義好每個組件的能力,把「這個組件能提供的能力」封裝成一個介面類放到base組件里,這是一種好方法,但是還有一些問題解決不了,
例如一些物體類(BookInfo、AuthorInfo),一些共用的資源檔案,圖片、UI布局、shape、文案,還是要放到 lib_base里,因為服務管理不能提供物體類和資源檔案的共用,
放 lib_base 最不好的一點是,這些資源只有書城和書架兩個組件用到,我的組件不會用到,它們不能成為 通用資源,
對于這種專案,某部分組件依賴和共用很嚴重的,可以按照 2.4 ARouter的服務管理 (還能優化的空間) 來設計,

作品詳細介紹頁面、作品章節串列、作者詳細介紹這3個頁面Activity放到業務組件里,可以使用ARouter跳轉到,
而相關能力介面、物體Bean、resources等這些共用的資源,就可以放到export層里面了,
比如這里我們開發小組討論協商,先劃分一些功能歸屬,
module_書城_export 負責 作品詳細介紹頁面、作者詳細介紹,所以 BookDetailEntity.kt、AuthorDetailEntity.kt 放到這個組件里,
module_書架_export 負責 作品章節串列,所以 BookChapter.kt 放到這個組件里,
代碼結構

1.Provider
使用ARouter的服務管理能力,在export組件創建一個Provider介面,在這里是 BookshelfProvider.kt,在這個案例里提供了的能力如上圖,
一個重點是,這些能力可以是立即return回傳回來,也可以通過設立回呼Callback,或者是利用rxjava的一個請求,
module_bookshelf_export 定義能力介面,module_bookshelf 寫具體實作這些能力的方法,
如果一個介面過于雜亂,還可以分為多個,
舉個例子創建一個provider包,有3個Interface檔案,對應三個細分能力的分類
- BookHistoryProvider.kt 表示 書架Tab 里面的 歷史閱讀書籍 串列
- BookLocalProvider.kt 表示 書架Tab 里面的 手機本地書籍 串列
- BookSubscriptionProvider.kt 表示 書架Tab 里面的 用戶訂閱書籍 串列
2.物體
比如 getBookShelfList 獲取書架的所有作品串列 這個介面,不可能回傳字串讓其他組件決議吧,所以相關的共用的物體類也應該定義在這里,
3.資源檔案
各類資源檔案,包括自定義View,這類都是Provider無法提供的能力,也應該定義在這里,
總結
- 判斷某塊功能需不需要獨立成組件?判斷標準是 「是否需要依賴不同的業務層組件,打成不同的包」
- 代碼應該放在哪里:
區分:哪些是所有業務組件都需要的能力,哪些是一部分業務組件需要的能力,哪些是只有自己用的能力,
| 使用范圍 | 放置位置 |
|---|---|
| 只有自己使用 | module_業務 |
| 需要給個別業務組件使用 | module_業務_export |
| 全部業務組件都使用 | lib_base |
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/261411.html
標籤:其他
