[TOC]
## 原架構模型
單一工程模型:邏輯分層模型應該也是最常見的項目結構模型,口袋助理現有的框架,就是基于邏輯分層模型的拓展。圖中可以看出,app下的目錄要將近80+個,其中:
* 有屬于業務層的目錄(cloud、crm\_order等)
* 有屬于中間件層的目錄(acl、advert等)
* 也有基礎組基層的目錄(utils、ui、uin等)


## MVC,MVP,MVVM
[Android App的設計架構:MVC,MVP,MVVM與架構經驗談](https://www.tianmaying.com/tutorial/AndroidMVC),
## 組件化 模塊化 插件化
組件化和模塊化是很類似的一對概念,原則上說在一些場景上進行同義替換是沒有問題的,兩者都是對代碼結構的“由大到小”的調整,兩者的目的都是為了重用和解耦.
* 模塊化
* 避免重復造輪子,節省開發維護成本;
* 降低項目復雜性,提升開發效率;
* 多個團隊公用同一個模塊,在一定層度上確保了技術方案的統一性
* 組件化
* 業務模塊間解耦
* 單個業務模塊單獨編譯打包,加快編譯速度
* 多團隊間并行開發、測試
如果一定要進行區分組件化和模塊化的話
模塊化
* 更類似于一種功能的切分,比如很多公司都常見的,網絡請求模塊、登錄注冊模塊單獨拿出來,交給一個團隊開發,而在用的時候只需要接入對應模塊的功能就可以了
* 在代碼結構上沒有過多的要求,不同模塊的源代碼可以直接放在放在主項目下
* 在編譯上,模塊化下的模塊必須依賴于主項目,在主項目中被調用和集成后,才能進行測試和運行
組件化
* 更傾向于業務的切分,比如打開淘寶或者大眾點評來看看,里面的美食、電影、酒店、外賣就是一個一個的業務
* 在代碼結構上,組件化有更高的要求,一般來講都需要單獨的獨立目錄,并且組件之間保持低耦合
* 在編譯上,Debug期間,組件可以不依賴于主項目,而單獨進行測試和運行,甚至可以獨立打包發布;Release版本下,則可以靈活的作為aar組合式的實現主項目功能;
* 插件化則不同,插件化是在開發時就將整個app拆分成很多apk模塊,這些模塊包括一個宿主和多個插件,每個模塊都是一個apk(組件化的每個模塊是個lib),最終打包的時候將宿主apk和插件apk分開或者聯合打包
* 可以這么理解:插件化是在[運行時],而組件化是在[編譯時]。換句話說,插件化是基于多 APK 的,而組件化本質上還是只有一個 APK。
## 主App多Lib開發模型
借助組件化這一思想,我們在”單工程”模型的基礎上
* 將業務層中的各業務抽取出來,封裝成相應的業務組件;
* 將基礎庫中各部分抽取出來,封裝成基礎組件
* 主工程是一個可運行的app,作為各組件的入口(主工程也被稱之為殼程序)
* 這些組件或以jar的形式呈現,或以aar的形式呈現.主工程通過依賴的方式使用組件所提供的功能

## 模塊化實施之路
### 要求
1. 必須與其他開發并行
2. 工作量要可控,可執行分步執行
3. 控制風險(不改變原意)
### 步驟
第一環節:從app module向下獨立出基礎組件
第二環節:將整個原來的app module作為中間層,向上獨立出業務 module. 盡量不要并行,會導致混亂
第三環節:將app和中間層作為SDK,內部更新,不干擾上層業務module層
## 問題一:拆分模塊的粒度
## 問題二:各個app中的Application 沖突和聚合
### 困境
每一個組件都會需要【調用原始Application內部函數調用】
每一個組件都會需要【感知Application生命周期】
### 解決內部函數調
方法下沉
### 解決生命周期
其實解決思路很簡單, 無非就是在開發時讓每個組件可以獨立管理自己的生命周期, 在運行時又可以讓每個組件的生命周期與宿主的生命周期進行合并 (在不修改或增加宿主代碼的情況下完成)
### 目前方案
類似觀察者模式,譬如注冊點擊事件
2. 在基礎層中定義有生命周期方法 (attachBaseContext(), onCreate() ...) 的接口 AppLifecyclesInterface
3. 在基礎層中提供一個用于管理組件生命周期的管理類 ConfigModule
4. 每個組件都手動將自己的生命周期實現類AppLifecycles注冊進這個管理類
5. 在集成調試時, 宿主在自己的 Application對應生命周期方法中通過管理類去遍歷調用注冊的所有生命周期實現類即可(通過反射AndroidManifest.meta對應路徑方式)
6. 在調試時,每個業務module 都使用基類的 BaseApplication
### 其余方案
* 使用 AnnotationProcessor,解析注解在編譯期間生成源代碼自動注冊所有組件的生命周期實現類, 然后宿主再在對應的生命周期方法中去調用//無法動態修改
* 在App中添加配置文件app\_libraries.properties,配置文件中添加對應的組件的Application,App初始化時讀取配置文件中的內容通過反射調用組件中的Application的生命周期方法。
## 問題三:Gradle文件的統一配置依賴管理
由于有諸多的module和子工程,如果各個子工程隨意引入第三方工具包或不同版本的三方包,勢必會導致軟件項目的混亂
因此對各個module的Gradle文件進行統一配置管理也是十分有必要的
1. 詳細可參看?[Gradle依賴的統一管理](https://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=402733201&idx=1&sn=052e12818fe937e28ef08331535a179e&scene=23&srcid=0313t2uhyzGx2iNSAllnKimj#rd)
2. 更進一步的,由于很多組件都需要buid.gradle,里面基本很多東西都是一樣的,可以在主工程新建一個文件夾并創建一個模板.gradle, 在其他module中直接引入,譬如[AndroidModuleDemo](https://github.com/hongxialiu/AndroidModuleDemo)
~~~
apply from: rootProject.file('script/library_work.gradle')
~~~
## 問題四:被依賴module中BuildConfig.DEBUG的值總為false
在Android的實際開發中,一般會有這樣的需求,debug和release版本不同,接口地址不同,同時控制日志是否打印等,系統為我們提供了一個很方便的類BuildConfig可以自動判斷是否是debug模式
有了BuildConfig.DEBUG之后,你在代碼中可以直接寫入
~~~
if (BuildConfig.DEBUG) {
Log.d(TAG, "output something");
}
~~~
但是在Android Studio中,被依賴module里BuildConfig.DEBUG的值總為false,因為Library projects目前只會生成release的包.
例如module A依賴module B和module C,在Eclipse里運行時B和C里BuildConfig.DEBUG的值會是true(導出簽名apk后會自動變成false);
然而在android Studio里B和C里的BuildConfig.DEBUG值總是false,A里的正常。這樣就導致if(BuildConfig.DEBUG){Log.d(...)}日志無法正常顯示
具體解決方法參見?[(2.2.8.9) 解決被賴module中BuldConfig.DEBUG的值總為false問題](http://blog.csdn.net/fei20121106/article/details/73912634)
## 問題五:資源重名
因為我們拆分出了很多業務組件和功能組件,并在最后一起打包處理,在合并過程中就有可能會出現資源名沖突問題,例如A組件和B組件都有一張叫做“ic\_back”的圖標,這時候在集成模式下打包APP就會編譯出錯
解決方式,總的來說可以分為兩種方式:前綴限制和統一管理
我們可以混雜使用
* 全部圖標、顏色、動畫、raw、values、menu素材全部放入 Basic Component Layer 基礎組件層的 CommonRes中
* 涉及公共的layout素材放入Basic Component Layer 基礎組件層的 CommonRes中
* Middleware Layer 中間件層:各個中間件module的layout xml使用 前綴限制
* Business Module Layer 業務 Module 層:各個業務module的layout xml使用 前綴限制
* 前綴限制
* 因為分了多個 module,在合并工程的時候總會出現資源引用沖突,比如兩個 module 定義了同一個資源名。
這個問題也不是新問題了,做 SDK 基本都會遇到,可以通過設置 resourcePrefix 來避免。設置了這個值后,你所有的資源名必須以指定的字符串做前綴,否則會報錯。
~~~
andorid{
defaultConfig {
resourcePrefix "moudle_prefix"
}
}
~~~
但是 resourcePrefix 這個值只能限定 xml 里面的資源,并不能限定圖片資源,所有圖片資源仍然需要你手動去修改資源名
另外一種方式是,將應用使用到的所有 res資源放到一個單獨module中進行統一管理,尤其是圖片和xml資源
## 問題六:解決重復依賴
### 使用api
默認情況下,如果是 aar 依賴,gradle 會自動幫我們找出新版本的庫而拋棄舊版本的重復依賴。
但是如果你使用的是 project 依賴,gradle 并不會去去重,最后打包就會出現代碼中有重復的類了。
通過將子App中的 compile 改為 provided,可實現只在最終的項目中 compile 對應的代碼;
### exclude 排除重復三方包
## 問題七:混淆方案
組件化項目的Java代碼混淆方案采用在集成模式下集中在app殼工程中混淆,各個業務組件不配置混淆文件。集成開發模式下在app殼工程中build.gradle文件的release構建類型中開啟混淆屬性,其他buildTypes配置方案跟普通項目保持一致,Java混淆配置文件也放置在app殼工程中,各個業務組件的混淆配置規則都應該在app殼工程中的混淆配置文件中添加和修改。
## 問題八:R.java文件的常量問題

尤其注意一點,在app中的R.string.xx這樣標量是一個 static final靜態常量,而 lib中的 R.string.xx 則是static靜態量,這是由于android的打包機制決定的,目前無法改變
因此在Debug模式下開發的時候,一定記得不能把 R中的變量 作為常量使用,譬如 switch case
## 問題九:業務組件組件化
### 數據庫組件化
### 搜索組件化
### 建聯推送組件化
## 問題十:?跨組件通信
因為各個業務模塊之間是各自獨立的, 并不會存在相互依賴的關系, 所以一個業務模塊是訪問不了其他業務模塊的代碼的, 如果想從 A Module的 A 頁面跳轉到 B Module的 B 頁面, 或者 在A Module 中調用 B Module的某個函數,光靠模塊自身是不能實現的, 所以這時必須依靠外界的其他媒介提供這個跨組件通信的服務;
跨組件通信主要有以下兩種場景:
### 頁面跳轉與函數調用
統一對外暴露的接口,與使用什么路由框架無關,都可以做的可替換
需要值得注意的是:[Android架構建設之組件化、模塊化建設](https://blog.csdn.net/xinanheishao/article/details/79980286)提出了一種基于ContentProvider 的交互方式,值得思考
### class引用
這個主要靠類下沉
雖然可以使用 反射 或者 序列化,但這兩者都不利于后續維護
## 推薦閱讀
[微信Android模塊化架構重構實踐](https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286672&idx=1&sn=4d9db00c496fcafd1d3e01d69af083f9&chksm=8334cc92b4434584e8bdb117274f41145fb49ba467ec0cd9ba5e3551a8abf92f1996bd6b147a&mpshare=1&scene=1&srcid=06309KcVegxww8kRannKXmkM&key=9965dca0b72a0a7428febd95a3bc61657924797129ae35d34f67f2cfc5c5ac09bec624714cd4662b978742d3424726f08b3ea1b9cb858cccf97dbb56bd5bfdd07a81917eedc452194d3c6b438d76dfac&ascene=0&uin=Mjg5NTY2MjM0MA==&devicetype=iMac%20MacBookPro11,4%20OSX%20OSX%2010.12.5%20build(16F73)&version=12020810&nettype=WIFI&fontScale=100&pass_ticket=X8yiKyEXbEsX7ouYBsjW0ddHl5Zc0CXaGzDaapndysc89C7Z257hmzlRaR3CQk)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺