單項目結(jié)構:
將程序的所有功能以及依賴庫都集中在一個項目下進行管理厕诡,不同業(yè)務或非業(yè)務通過包名區(qū)分灵嫌,使得項目結(jié)構清晰,比如常見的登錄猖凛、反饋辨泳、上報功能等玖院。
-
優(yōu)點:結(jié)構簡單难菌,適合小團隊郊酒、需要快速迭代且復雜度不高的產(chǎn)品
適合:產(chǎn)品處于探索期试读、未成形钩骇、或是需要快速迭代和驗證功能倘屹、總體功能未穩(wěn)定
缺點:擴展性相對較差纽匙,模塊間耦合性較高烛缔,不利于大型項目的開發(fā)
適合小項目和需要快速迭代開發(fā)的項目践瓷。
插件化:
把 App 拆分成一個宿主和多個插件晕翠,插件可以在運行期動態(tài)加載
出現(xiàn)背景:
- 基于開發(fā)考慮,項目過于龐大時通過插件化解耦
- 基于運行考慮淋肾,通過插件化進行動態(tài)的功能運營
- 基于質(zhì)量考慮硫麻,線上熱修復(HotFix)
特點:重樊卓、黑科技拿愧;對團隊和技術要求高简识。每個組負責單獨插件開發(fā)赶掖,適合航母級應用感猛。
組件化:
將一個 App 按照功能或者業(yè)務拆分為多個模塊,每個模塊作為單獨的組件(module)陪白,可以獨立開發(fā)和調(diào)試咱士,最終在發(fā)布時锐膜,再將這些組件合并成完整的 Apk。
- 組件化有優(yōu)點,當然也有代價荷逞,真正有必要才去做,沒必要完全不用做這種事情粹排。
為什么需要組件化种远?
高內(nèi)聚代碼的強制解耦
-
各組件相互獨立,便于開發(fā)和調(diào)試
功能點比較少的項目顽耳,通過單項目工程就足以應付開發(fā)場景坠敷,當功能比較多的時候妙同,比如集成一個直播、社交功能..功能點獨立常拓,完全可以由其它開發(fā)團隊來開發(fā)渐溶、或根據(jù)模塊由不同開發(fā)人員進行開發(fā) (節(jié)省時間,提高開發(fā)效率)
-
便于項目的集成和更新
比如這個組件并不在這個版本上使用弄抬,但是要先開發(fā)出來茎辐,等到下一個版本需要上的時候這個組件能夠非常快集成進項目中去掂恕。
-
易于復用和擴展
UI庫樣式一致拖陆、意見反饋這些模塊,隨時可以扔到另外的項目中去用懊亡。
-
有損服務依啰,動態(tài)加載
Android 生態(tài)復雜,手機性能差異巨大店枣,有損服務出現(xiàn)在插件化概念中速警,可以通過判斷用戶手機性能,如果性能高就加載所有組件鸯两,如果比較差就先加載核心的闷旧,其他一些組件可能需要用戶去開啟或者去下載的 。(插件化范疇了)
...
組件化主流方案
-
馮森林 MDCC 2016 中國移動開發(fā)者大會
多module钧唐,debug時候是apk忙灼,發(fā)布時作為library
-
大眾點評 (AAR 獨立 repo)
組件放到單獨的 git 倉庫中,打包生成 aar 钝侠,供宿主或其他組件調(diào)用
-
淘寶 Atlas 動態(tài)組件化(Dynamic Bundle)框架
類似 OSGI 该园,其實是一種插件化方式
組件化模型
-
單工程模型:
業(yè)務組件和基礎庫在一個項目里面,只是通過分包名進行區(qū)分帅韧,組件之前耦合性嚴重里初。各組件調(diào)用強引用。
-
組件化模型
- 最上層是業(yè)務組件忽舟,比如 新聞双妨、直播、社交萧诫、意見反饋組件等等斥难,會用到下層的圖片庫網(wǎng)絡庫(基礎庫)
- 各組件通過通信模塊(ARouter)間接進行通信。
- App外殼作為宿主帘饶,這個外殼加載包裝其他組件哑诊,類似手機操作系統(tǒng),安裝不同App及刻。
- 最低層是UI庫和基礎庫(UI庫比較少人做,一般只是基礎庫CommonLib)
組件化具體實踐
1. 組件依賴方式
-
AAR 依賴镀裤,
子module全部打包生成aar竞阐,由一個殼工程去組裝構建 App,各業(yè)務模塊獨立拆分Git倉庫暑劝,提高項目隔離性骆莹。
優(yōu)點:隔離性好
缺點:上層依賴底層,發(fā)布順序必須等到依賴的底層 AAR 開發(fā)完畢才能發(fā)布担猛,有一定依賴性 -
Compile project 依賴
Module 在 debug 模式下作為Application幕垦,在release模式下作為library,各組件在調(diào)試時可以獨立運行傅联,在 App 發(fā)布時作為lib嵌入主項目先改。(簡單、常用)
2. 組件獨立編譯
開發(fā)模式下蒸走,子模塊可以單獨調(diào)試仇奶,生成獨立的 App,而在主程序發(fā)布時比驻,則作為 library嵌入主程序该溯,在子模塊的
build.gradle
配置中,可根據(jù)常量判斷是否處于開發(fā)模式
-
根目錄配置文件中配置各個模塊組件是否處于 App 還是 Module别惦。
-
項目根目錄
gradle.properties
配置:# 是否需要單獨編譯 true表示不需要狈茉,false表示需要 #isApp_Home=false #isApp_Chat=false #isApp_Video=false #isApp_Me=false
-
在各個子模塊中配置(例如Home Module):
if (isApp_Home.toBoolean()) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' }
-
-
applicationId,只有在 application 的情況下才需要聲明步咪。
defaultConfig { if (isApp_Home.toBoolean()) { //單獨運行時候需要 applicationId applicationId "tsou.cn.module_me" } }
-
sourceSets:在debug模式下生成自己的清單文件(需要入口函數(shù)或測試類作為一個獨立app來運行的時候)
sourceSets { main { if (isDebug.toBoolean()) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { manifest.srcFile 'src/main/release/AndroidManifest.xml' java { exclude 'debug/**' } } } }
-
在app主模塊中:
if (isNeedHomeModule.toBoolean()) { compile project (':module_home') } if (isNeedChatModule.toBoolean()) { compile project (':module_chat') } if (isNeedRecomModule.toBoolean()) { compile project (':module_recom') } if (isNeedMeModule.toBoolean()) { compile project (':module_me') }
3. 組件 SDK 版本一致性
根目錄定義ext統(tǒng)一版本论皆,子module引用益楼。避免版本沖突
-
project 的 build.gradle 中定義常量
ext { minSdkVersion = 16 supportVersion='27.1.1' }
-
組件的
build.gradle
引用常量defaultConfig { minSdkVersion rootProject.ext.minSdkVersion } dependencies { api("com.android.support:appcompat-v7:${supportVersion}") ... }
4. 資源合并沖突(*)
合并沖突: 是指多個Manifest文件中含有同一屬性但值不同時猾漫,默認合并規(guī)則解決不了從而導致的沖突。
當沖突發(fā)生時感凤,高優(yōu)先級的Manifest屬性值會覆蓋低優(yōu)先級屬性值悯周。這個優(yōu)先級規(guī)則由高到低依次是:buildType下的Manifest設置 > productFlavor下的Manifest設置 > 主工程src/main > dependency&library
不同 module 資源id合并:
- 如果兩個模塊中定義了相同資源id,將使用應用中的資源id
- 如果多個 AAR 庫之間發(fā)生沖突陪竿,將使用依賴項列表首先列出(位于dependencies塊頂部)的庫中的資源
- 為避免資源id沖突禽翼,請使用在模塊中具有唯一性前綴或其他一致性的命名方案
- 3.1:Gradle 設置資源前綴:
resourcePrefix "user_"
- 3.2:修改 aapt(入侵了源碼和app編譯過程,存在風險)
- 3.1:Gradle 設置資源前綴:
-
模塊私有資源:
私有資源描述可參考如果不想讓其他 module 訪問我當前 module 的資源族跛,可以申明私有屬性
1)資源庫中所有資源默認處于公開狀態(tài)
2)要將所有資源隱私設為私有闰挡,至少將一個特定的屬性定義為公開
3)在res/value目錄下,創(chuàng)建public.xml文件礁哄,定義公開資源<resources> <public name="mylib_main_layout" type="layout"/> <public name="mylib_public_string" type="string"/> </resources> 除了以上定義的共有資源以外的都是私有資源
-
AndroidManifest.xml 合并沖突
Android Studio工程通常包含多個AndroidManifest文件长酗,最終構建成APK時,會合并成一個AndroidManifest文件桐绒。
合并沖突: 是指多個Manifest文件中含有同一屬性但值不同時夺脾,默認合并規(guī)則解決不了從而導致的沖突之拨。
當沖突發(fā)生時,高優(yōu)先級的Manifest屬性值會覆蓋低優(yōu)先級屬性值咧叭。這個優(yōu)先級規(guī)則由高到低依次是:
buildType下的Manifest設置 > productFlavor下的Manifest設置 > 主工程src/main > dependency&library合并規(guī)則標記:
1. 節(jié)點標記:
tools:node="replace"
:在高優(yōu)先級 Manifest 中添加蚀乔,完全替換低優(yōu)先級
2. 屬性標記:
tools:remove="attr"
3. 標記選擇器:
selector
5. 組件間解耦(頁面跳轉(zhuǎn)解耦+組件間通信解耦):
Splash(殼工程)--> 首頁(module)-->意見反饋(module)
以上三個需要交互的頁面處于三個不同的Module,組件不可能完全拆分的干凈菲茬,勢必會有交互和通信吉挣;
-
Android 原生支持 URL Scheme/AIDL(Service)
protocol://host:port/path?params <data android:scheme="protocol" android:host="host" android:port="8080" android:path="/path" /> Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("url"));
Scheme 跳轉(zhuǎn)解耦每個頁面都需要在Manifest配置,擴展性差婉弹,跳轉(zhuǎn)過程無法控制听想,參數(shù)傳遞困難 。 適合H5跳轉(zhuǎn)到原生應用马胧,如果原生跳轉(zhuǎn)使用這個的話汉买,規(guī)則太多,清單文件都需要去定義佩脊,非常不靈活蛙粘。
AIDL(Service) 解耦,各組件都需要維護AIDL文件威彰,相對比較復雜出牧。
-
基于注解的路由框架(Arouter)
特點:
- 直接URL路由&參數(shù)解析賦值
- 支持多模塊項目
- 支持InstantRun
- 允許自定義攔截器(AOP)
- 提供IOC容器(控制反轉(zhuǎn))
- 隱射關系自動注冊
- 靈活的降級策略
路由編譯注解:
給每個Activity/Fragment都加上一個注解標記(路徑),最后編譯的時候把這些路徑都生成在自己組件 apt 目錄(build)下歇盼,真正運行的時候舔痕,掃描所有組件的路徑,得到所有的映射關系豹缀,path(key)對應Activity(value)伯复,path 對應 Activity,跳轉(zhuǎn)的時候就可以找到路徑進行跳轉(zhuǎn)邢笙。其實是個Map<String,RouteMeta>,好處是啸如,所有的映射關系不是通過反射完成,而是在編譯期間生成氮惯。攔截編譯注解:
組件化跳轉(zhuǎn)時候不知道其他組件申明了什么Activity叮雳,直接跳轉(zhuǎn)有可能找不到對應Activity,所以這里應該有一種降級的策略妇汗,如果沒有找到對應Activity帘不,可以提示用戶而不是直接Crash⊙罴或者是驗證登錄寞焙。-
參數(shù)注入注解:
@Autowired String name;
ARouter特點:
1)APT編譯期生成關系映射表
2)運行期通過路徑查找對應頁面進行跳轉(zhuǎn)
3)頁面跳轉(zhuǎn)完全解耦,不需要訪問你那個類
4)缺點是 path告唆、params非常量棺弊,可能會寫錯(需要自己管理)
ARouter不同組件接口調(diào)用:
提供IProvider:
面向接口編程:通過接口進行通信晶密。
通信雙方必須共同依賴該接口(缺點)。
如果我沒有這個接口根本就調(diào)不到模她。
可以寫到CommonLib中稻艰,但是不是好的方式。
組件間解耦思考侈净?
- 組件間提供的頁面以及接口路徑尊勿,是以字符串還是常量的方式?如果是常量形式畜侦,常量放在哪元扔?
- 組件間數(shù)據(jù)傳遞,非普通類型如何傳遞旋膳?Bean 應該放在哪個模塊澎语?
需要傳遞的Bean位置:依賴包或CopyTask拷貝到CommomLib(公共的地方其實也就是)
解決方案:
創(chuàng)建組件的時候,同時創(chuàng)建該組件的依賴包验懊。
組件通過Service依賴包提供自身可以被訪問的頁面路徑常量擅羞,接口常量,以及需要傳遞參數(shù)的所有的Bean义图。 Module 之間依賴對方的 依賴包减俏,通過依賴包中的公共部分提供通信基礎。
缺點是組件多的時候 Module 會變 Double碱工,解決方案是通過 Gradle Copy Task 拷貝到CommonLib 目錄下娃承,具體:
依賴包不用單獨建立Module,直接寫在自己Module中怕篷,按照一定規(guī)則的路徑存放历筝。然后通過Gradle的腳本拷貝到 CommonLib目錄下。(其實最后還是放到了公共的CommonLib目錄下)
6. 組件初始化
組件何時何地初始化:每個module實現(xiàn)一個入口類匙头,用于統(tǒng)一調(diào)度(類似Application)漫谷。
7. 組件化可能遇到的坑
-
R文件:
App 項目生成的 R 文件是 static final(常量)的仔雷, ADT14后 library 項目中生成的并不是final類型蹂析。
影響:- switch...case...
- ButterKnife 注解
其依賴于常量,所以當R文件不是常量時會失效碟婆。(R2或放棄使用..)
-
庫發(fā)布
默認情況下电抚,library只發(fā)布release版本,當app時debug版本(未混淆)竖共,而 library 是已經(jīng)混淆過的蝙叛,而會導致編譯問題。
具體可參考一公给、在library module中的build.gradle中設置如下:
android{ publishNonDefault true //不讓發(fā)布默認release版本 }
二借帘、在主 module 的build.gradle設置如下:
dependencies { releaseCompile project(path: ':library', configuration: 'release') debugCompile project(path: ':library', configuration: 'debug') //flavor1Compile project(path:‘:lib_name’,configration:'release') //flavor2Compile project(path:‘:lib_name’,configration:'debug') }
這樣可以讓app和library的debug和release保持一致
-
重復依賴:provided project 蜘渣、exclude module
-
Project 重復依賴
if(isApp_News.toBoolean()){ compile project(':CommonLib') }else{ provided project(': CommonLib')//去除依賴 }
-
Duplicate entry(多個入口)
//exclude 命令 compile('com.jakewharton:butterknife:8.5.1'){ exclude module:'support-compat' }
-
- 其他:
DataBinding、Dagger肺然、Retrolambda等第三方蔫缸、多渠道打包、混淆和加固际起、Application Context
相關認識:
組件化的實施對開發(fā)人員和團隊管理者提出了更高水平的要求.相對傳統(tǒng)方式,在項目的管理和組織上難度加大,要求開發(fā)人員對業(yè)務有更深層次上的理解.
組件化首要做的事情就是劃分組件.如何劃分并沒有一個確切的標準,建議早期實施組件化的時候,可以以一種”較粗”的粒度來進行,這樣的好處在于后期隨著對業(yè)務的理解和熟悉進行再次細分,而不會有太大的成本
這樣的技術其實對于純開發(fā)而言難度是不大的拾碌,真正的難度在于如何剝離現(xiàn)有的業(yè)務線。粒度大拆分比較容易街望,但是不利于今后的維護校翔。粒度小需要對業(yè)務有很深的理解,但是能很好的解耦并且提高靈活度灾前,所以具體的情況需要在具體的實際開發(fā)中進行分析防症。
組件化開發(fā)不是銀彈,并不能完全解決當前業(yè)務復雜的情況,在進行項目實施和改進之前,一定要多加考量.
對當前項目實施組件化我的一些理解
組件化優(yōu)點:
- 提高團隊開發(fā)效率(并行開發(fā))
- 項目結(jié)構清晰(多個業(yè)務 Module)
- 降低代碼耦合性(各個業(yè)務組件相互隔離)
- 復用已有組件
一些組件化參考鏈接:
Android-組件化如何處理多個ModuleApplication共存問題?
使用阿里ARouter路由實現(xiàn)組件化(模塊化)開發(fā)流程+源碼
Android徹底組件化demo發(fā)布-得到App方案
Android組件化框架設計與實踐
我所理解的Android組件化之通信機制
Demo