文檔當前狀態:**beta0.4**
* [x] 選題收集:2017/11/20
* [x] 初稿整理:
* [ ] 補充校對:
* [ ] 入庫存檔:
---
[TOC]
---
**引言**
在移動開發中,我想大部分開發人員都會面臨一個問題,隨著項目的開發與不斷的迭代,業務也會隨之不斷壯大,也就意味著業務模塊越來越多,這個時候就會出現一個問題,各個模塊之間的相互引用,導致模塊之間的耦合越來越嚴重,最終迭代會牽一發而動全身,項目的迭代和維護都特別困難;一些門戶型的APP還伴隨著子應用單獨包裝推廣,影子應用單獨發布等等需求,那么一個合適的應用框架的需求是必不可少的,接下來我們會對Android組件化 實踐進行探討,以圖能在需求日益復雜多變的情況下,我們也能如期交付。
那么這篇文章就通過組件化來分析Android模塊解耦的實現,實現多人真正的協同開發,每一個模塊實現
### 一、模塊化、組件化與插件化
項目發展到一定程度,隨著人員的增多,代碼越來越臃腫,這時候就必須進行模塊化的拆分。模塊化是一種指導理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何實施,目前有兩種途徑,也是兩大流派,一個是組件化,一個是插件化。
提起組件化和插件化的區別,有一個很形象的圖:

組件化和插件化對比.png
上面的圖看上去比較清晰,其實容易導致一些誤解,有下面幾個小問題,圖中可能說的不太清楚:
* 組件化是一個整體嗎?去了頭和胳膊還能存在嗎?左圖中,似乎組件化是一個有機的整體,需要所有器官都健在才可以存在。而實際上組件化的目標之一就是降低整體(app)與器官(組件)的依賴關系,缺少任何一個器官app都是可以存在并正常運行的。
* 頭和胳膊可以單獨存在嗎?左圖也沒有說明白,其實答案應該是肯定的。每個器官(組件)可以在補足一些基本功能之后都是可以獨立存活的。這個是組件化的第二個目標:組件可以單獨運行。
* 組件化和插件化可以都用右圖來表示嗎?如果上面兩個問題的答案都是YES的話,這個問題的答案自然也是YES。每個組件都可以看成一個單獨的整體,可以按需的和其他組件(包括主項目)整合在一起,從而完成的形成一個app
* 右圖中的小機器人可以動態的添加和修改嗎?如果組件化和插件化都用右圖來表示,那么這個問題的答案就不一樣了。對于組件化來講,這個問題的答案是部分可以,也就是在編譯期可以動態的添加和修改,但是在運行時就沒法這么做了。而對于插件化,這個問題的答案很干脆,那就是完全可以,不論實在編譯期還是運行時!
本文主要集中講的是組件化的實現思路,對于插件化的技術細節不做討論,我們只是從上面的問答中總結出一個結論:組件化和插件化的最大區別(應該也是唯一區別)就是組件化在運行時不具備動態添加和修改組件的功能,但是插件化是可以的。
暫且拋棄對插件化“道德”上的批判,我認為對于一個Android開發者來講,插件化的確是一個福音,這將使我們具備極大的靈活性。但是苦于目前還沒有一個完全合適、完美兼容的插件化方案(RePlugin的饑餓營銷做的很好,但還沒看到療效),特別是對于已經有幾十萬代碼量的一個成熟產品來講,套用任何一個插件化方案都是很危險的工作。所以我們決定先從組件化做起,本著做一個最徹底的組件化方案的思路去進行代碼的重構,下面是最近的思考結果,歡迎大家提出建議和意見。[引用原文](http://www.jianshu.com/p/1b1d77f58e84)
### 一、組件化概述
組件化開發就是將一個APP分成多個模塊,每個模塊我們都可以看成是一個組件,也就是在IDE中所創建的(Module),在開發的過程中我們可以對這些組件進行單獨調試,編譯和運行以及進行單獨的版本控制等,但是最終發布的時候是將這些組件合并統一成一個apk,這就是組件化開發。對此,組件化也有三個方面的要求:
* **可以單獨編譯**(不對其他module強依賴)
* **可以單獨運行**(開發時以Application運行,發布時Libary)
* **可以單獨的版本控制**(可選,主要用于超大型項目,既可以獨立app發布,又可以集成到門戶app中)
### 二、組件化的優勢
#### 1.提升編譯速度
在項目達到一定的規模的時候,編譯是一件十分痛苦的事情,短則幾分鐘,長則十幾分鐘。我們在修改了很小一塊代碼就要去編譯整個項目,這樣會導致開發效率降低;通過組件化,在實際開發過程中,我們可以僅僅選擇自己負責的那一個Module,排除其他不必要的module,單獨編譯就會極大的改善這個問題;
#### 2.適應并行開發
通過組件化可以更好的去適應并行開發,因為我們可以為每一個模塊進行單獨的版本控制,甚至每一個模塊的負責人可以選擇自己的設計架構而不影響其他模塊的開發,與此同時組件化還可以避免模塊之間的交叉依賴,每一個模塊的開發人員可以對自己的模塊進行獨立測試,獨立編譯和運行,甚至可以實現單獨的部署。從而極大的提高了并行開發效率。
### 二、實現組件化面對的問題
要實現組件化,不論采用什么樣的技術路徑,需要考慮的問題主要包括下面幾個:
**1.整體結構抽象**
* **代碼解耦**。如何將一個龐大的工程拆分成有機的整體?
* **代碼隔離**。組件之間的交互如果還是直接引用的話,那么組件之間根本沒有做到解耦,如何從根本上避免組件之間的直接引用呢?也就是如何從根本上杜絕耦合的產生呢?只有做到這一點才是徹底的組件化。
**2.Build系統改造**
* **單獨編譯**。在開發階段如何做到按需的編譯組件?一次調試中可能只有一兩個組件參與集成,這樣編譯的時間就會大大降低,提高開發效率。
* **組件單獨運行**。上面也講到了,每個組件都是一個完整的整體,如何讓其單獨運行和調試呢?
**3.實際代碼編寫**
* **數據傳遞**。因為每個組件都會給其他組件提供的服務,那么主項目(Host)與組件、組件與組件之間如何傳遞數據?
* **UI跳轉**。UI跳轉可以認為是一種特殊的數據傳遞,在實現思路上有啥不同?
* **組件的生命周期**。我們的目標是可以做到對組件可以按需、動態的使用,因此就會涉及到組件加載、卸載和降維的生命周期。
### 暫行的解決方式
**1.調整整體結構,實現解耦**
把龐大的代碼進行拆分,Androidstudio能夠提供很好的支持,使用IDE中的multiple module這個功能,我們很容易把代碼進行初步的拆分。在這里我們對兩種module進行區分:**基礎庫library**和**業務組件Component**。
* **基礎庫library**。這些代碼被其他組件直接引用。比如網絡庫module可以認為是一個library。
* **業務組件Component**。這種module是一個完整的業務功能模塊。比如讀書或者分享module就是一個Component。
為了方便,我們統一把library稱之為依賴庫,而把Component稱之為組件,我們所講的組件化也主要是針對Component這種類型。而負責拼裝這些組件以形成一個完成app的module,一般我們稱之為主項目、主module或者Host,方便起見我們也統一稱為主項目。
經過實際項目的嘗試,我們可能就可以把代碼拆分成下面的結構:

這種拆分都是比較容易做到的,從圖上看,讀書、分享等都已經拆分組件,并共同依賴于公共的依賴庫(簡單起見只畫了一個),然后這些組件都被主項目所引用。讀書、分享等組件之間沒有直接的聯系,我們可以認為已經做到了組件之間的解耦。但是這個圖有幾個問題需要指出:
* 從上面的圖中,我們似乎可以認為組件只有集成到主項目才可以使用,而實際上我們的希望是每個組件是個整體,可以獨立運行和調試,那么如何做到單獨的調試呢?
-》單獨編譯,可以通過配置Gradle進行處理,實現運行類型自定義。
* 主項目可以直接引用組件嗎?也就是說我們可以直接使用compile project(:reader)這種方式來引用組件嗎?如果是這樣的話,那么主項目和組件之間的耦合就沒有消除啊。我們上面講,組件是可以動態管理的,如果我們刪掉reader(讀書)這個組件,那么主項目就不能編譯了啊,談何動態管理呢?所以主項目對組件的直接引用是不可以的,但是我們的讀書組件最終是要打到apk里面,不僅代碼要和并到claases.dex里面,資源也要經過meage操作合并到apk的資源里面,怎么避免這個矛盾呢?
-》使用ARouter中,主項目一般僅依賴基礎庫,然后
* 組件與組件之間真的沒有相互引用或者交互嗎?讀書組件也會調用分享模塊啊,而這在圖中根本沒有體現出來啊,那么組件與組件之間怎么交互呢?
-》對于組件間交互這塊,實際場景多樣,這里主要說 兩種情況:
1. 直接跳轉頁面
2. 不跳轉頁面,僅調用組件中的部分功能
對于直接跳轉頁面 并夾帶數據的 可以直接使用ARtouer進行跳轉,對于第二點僅調用組件的部分功能,這點其實類似sdk的功能,現階段我們的簡單粗暴的做法是 直接把需要的代碼下沉到 基礎庫,現在來看確實能滿足業務需求,也足夠簡單,缺點就是未來業務越來越多后,基礎庫會原來越大,代碼越來越冗雜,有興趣的可以看看微信團隊分享的歷程。[微信 Android 模塊化架構重構實踐(上](https://cloud.tencent.com/community/article/441423)、[微信 Android 模塊化架構重構實踐 下](https://cloud.tencent.com/community/article/794491)。如果實在不能接受這種技術債務的方式,可以嘗試使用下面的抽象服務解決方式。
**抽象服務**
上面我們講到,主項目和組件、組件與組件之間不能直接使用類的相互引用來進行數據交互。那么如何做到這個隔離呢?在這里我們采用接口+實現的結構。每個組件聲明自己提供的服務Service,這些Service都是一些抽象類或者接口,組件負責將這些Service實現并注冊到一個統一的路由Router中去。如果要使用某個組件的功能,只需要向Router請求這個Service的實現,具體的實現細節我們全然不關心,只要能返回我們需要的結果就可以了。這與Binder的C/S架構很相像。
因為我們組件之間的數據傳遞都是基于接口編程的,接口和實現是完全分離的,所以組件之間就可以做到解耦,我們可以對組件進行替換、刪除等動態管理。這里面有幾個小問題需要明確:
● 組件怎么暴露自己提供的服務呢?在項目中我們簡單起見,專門建立了一個componentservice的依賴庫,里面定義了每個組件向外提供的service和一些公共model。將所有組件的service整合在一起,是為了在拆分初期操作更為簡單,后面需要改為自動化的方式來生成。這個依賴庫需要嚴格遵循開閉原則,以避免出現版本兼容等問題。
● service的具體實現是由所屬組件注冊到Router中的,那么是在什么時間注冊的呢?這個就涉及到組件的加載等生命周期,我們在后面專門介紹。
● 一個很容易犯的小錯誤就是通過持久化的方式來傳遞數據,例如file、sharedpreference等方式,這個是需要避免的。
下面就是加上數據傳輸功能之后的架構圖:

組件之間的數據傳輸
其實單獨調試比較簡單,只需要把apply plugin: 'com.android.library'切換成apply plugin: 'com.android.application'就可以,但是我們還需要修改一下AndroidManifest文件,因為一個單獨調試需要有一個入口的actiivity。
為此,我們可以添加了一個變量isApp,標記當前是否需要單獨調試,根據isApp的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。
此外,為了避免不同組件之間資源名重復,在每個組件的build.gradle中增加resourcePrefix "xxx_",從而固定每個組件的資源前綴。下面是讀書組件的build.gradle的示例:
~~~
if(isApp){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
.....
resourcePrefix "readerbook_"
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java','src/main/runalone/java']
res.srcDirs = ['src/main/res','src/main/runalone/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
~~~
通過這些額外的代碼,我們給組件搭建了一個測試Host,從而讓組件的代碼運行在其中,所以我們可以再優化一下我們上面的框架圖。

### 參考引文
[阿里ARouter開源組件化框架項目實踐](http://www.jianshu.com/p/735c969fdd9d)
[Android徹底組件化方案實踐](http://www.jianshu.com/p/1b1d77f58e84)
- 0-發現
- AndroidInterview-Q-A
- Android能讓你少走彎路的干貨整理
- LearningNotes
- temp
- temp11
- 部分地址
- 0-待辦任務
- 待補充列表
- 0-未分類
- AndroidView事件分發與滑動沖突處理
- Spannable
- 事件分發機制詳解
- 1-Java
- 1-Java-01基礎
- 未歸檔
- 你應該知道的JDK知識
- 集合框架
- 1-Java-04合集
- Java之旅0
- Java之旅
- JAVA之旅01
- JAVA之旅02
- JAVA之旅03
- JAVA之旅04
- JAVA之旅05
- JAVA之旅06
- JAVA之旅07
- JAVA之旅08
- JAVA之旅09
- java之旅1
- JAVA之旅10
- JAVA之旅11
- JAVA之旅12
- JAVA之旅13
- JAVA之旅14
- JAVA之旅15
- JAVA之旅16
- JAVA之旅17
- JAVA之旅18
- JAVA之旅19
- java之旅2
- JAVA之旅20
- JAVA之旅21
- JAVA之旅22
- JAVA之旅23
- JAVA之旅24
- JAVA之旅25
- JAVA之旅26
- JAVA之旅27
- JAVA之旅28
- JAVA之旅29
- java之旅3
- JAVA之旅30
- JAVA之旅31
- JAVA之旅32
- JAVA之旅33
- JAVA之旅34
- JAVA之旅35
- 1-Java-05辨析
- HashMapArrayMap
- Java8新特性
- Java8接口默認方法
- 圖解HashMap(1)
- 圖解HashMap(2)
- 2-Android
- 2-Android-1-基礎
- View繪制流程
- 事件分發
- AndroidView的事件分發機制和滑動沖突解決
- 自定義View基礎
- 1-安卓自定義View基礎-坐標系
- 2-安卓自定義View基礎-角度弧度
- 3-安卓自定義View基礎-顏色
- 自定義View進階
- 1-安卓自定義View進階-分類和流程
- 10-安卓自定義View進階-Matrix詳解
- 11-安卓自定義View進階-MatrixCamera
- 12-安卓自定義View進階-事件分發機制原理
- 13-安卓自定義View進階-事件分發機制詳解
- 14-安卓自定義View進階-MotionEvent詳解
- 15-安卓自定義View進階-特殊形狀控件事件處理方案
- 16-安卓自定義View進階-多點觸控詳解
- 17-安卓自定義View進階-手勢檢測GestureDetector
- 2-安卓自定義View進階-繪制基本圖形
- 3-安卓自定義View進階-畫布操作
- 4-安卓自定義View進階-圖片文字
- 5-安卓自定義View進階-Path基本操作
- 6-安卓自定義View進階-貝塞爾曲線
- 7-安卓自定義View進階-Path完結篇偽
- 8-安卓自定義View進階-Path玩出花樣PathMeasure
- 9-安卓自定義View進階-Matrix原理
- 通用類介紹
- Application
- 2-Android-2-使用
- 2-Android-02控件
- ViewGroup
- ConstraintLayout
- CoordinatorLayout
- 2-Android-03三方使用
- Dagger2
- Dagger2圖文完全教程
- Dagger2最清晰的使用教程
- Dagger2讓你愛不釋手-終結篇
- Dagger2讓你愛不釋手-重點概念講解、融合篇
- dagger2讓你愛不釋手:基礎依賴注入框架篇
- 閱讀筆記
- Glide
- Google推薦的圖片加載庫Glide:最新版使用指南(含新特性)
- rxjava
- 這可能是最好的RxJava2.x入門教程完結版
- 這可能是最好的RxJava2.x入門教程(一)
- 這可能是最好的RxJava2.x入門教程(三)
- 這可能是最好的RxJava2.x入門教程(二)
- 這可能是最好的RxJava2.x入門教程(五)
- 這可能是最好的RxJava2.x入門教程(四)
- 2-Android-3-優化
- 優化概況
- 各種優化
- Android端秒開優化
- apk大小優化
- 內存分析
- 混淆
- 2-Android-4-工具
- adb命令
- 一鍵分析Android的BugReport
- 版本控制
- git
- git章節簡述
- 2-Android-5-源碼
- HandlerThread 源碼分析
- IntentService的使用和源碼分析
- 2-Android-9-辨析
- LRU算法
- 什么是Bitmap
- 常見圖片壓縮方式
- 3-Kotlin
- Kotlin使用筆記1-草稿
- Kotlin使用筆記2
- kotlin特性草稿
- Kotlin草稿-Delegation
- Kotlin草稿-Field
- Kotlin草稿-object
- 4-JavaScript
- 5-Python
- 6-Other
- Git
- Gradle
- Android中ProGuard配置和總結
- gradle使用筆記
- Nexus私服搭建
- 編譯提速最佳實踐
- 7-設計模式與架構
- 組件化
- 組件化探索(OKR)
- 1-參考列表
- 2-1-組件化概述
- 2-2-gradle配置
- 2-3-代碼編寫
- 2-4-常見問題
- 2-9-值得一讀
- 8-數據結構與算法
- 0臨時文件
- 漢諾塔
- 8-數據-1數據結構
- HashMap
- HashMap、Hashtable、HashSet 和 ConcurrentHashMap 的比較
- 遲到一年HashMap解讀
- 8-數據-2算法
- 1個就夠了
- Java常用排序算法(必須掌握的8大排序算法)
- 常用排序算法總結(性能+代碼)
- 必須知道的八大種排序算法(java實現)
- 9-職業
- 閱讀
- 書單
- 面試
- 面試-01-java
- Java面試題全集駱昊(上)
- Java面試題全集駱昊(下)
- Java面試題全集駱昊(中)
- 面試-02-android
- 40道Android面試題
- 面試-03-開源源碼
- Android圖片加載框架最全解析(二),從源碼的角度理解Glide的執行流程
- 面試-07-設計模式
- 面試-08-算法
- 面試-09-其他
- SUMMARY
- 版權說明
- temp111