Android開發(fā)組件化架構(gòu)設(shè)計(jì)原理到實(shí)戰(zhàn)
為什么需要組件化
小項(xiàng)目是不需要組件化的。當(dāng)一個(gè)項(xiàng)目有數(shù)十個(gè)人開發(fā),編譯項(xiàng)目要花費(fèi)10分鐘,修改一個(gè)bug就可能會影響到其他業(yè)務(wù),小小的改動(dòng)就需要進(jìn)行回歸測試,如果是這種項(xiàng)目,那么我們需要進(jìn)行組件化了
組件化和模塊化
在技術(shù)架構(gòu)演進(jìn)的過程一定是先出現(xiàn)模塊化后出現(xiàn)組件化,因?yàn)榻M件化就是解決了模塊化的問題。
模塊化架構(gòu)
創(chuàng)建一個(gè) Project 后可以創(chuàng)建多個(gè) Module,這個(gè) Module 就是所謂的模塊。一個(gè)簡單的例子,可能在寫代碼的時(shí)候我們會把首頁、消息、我的模塊拆開,每個(gè) tab 所包含的內(nèi)容就是一個(gè)模塊,這樣可以減少 module 的代碼量,但是每個(gè)模塊之間的肯定是有頁面的跳轉(zhuǎn),數(shù)據(jù)傳遞等,比如 A 模塊需要 B 模塊的數(shù)據(jù),于是我們會在 A 模塊的 gradle 文件內(nèi)通過 implementation project(':B')依賴 B 模塊,但是 B 模塊又需要跳轉(zhuǎn)到 A 模塊的某個(gè)頁面,于是 B 模塊又依賴了 A 模塊。這樣的開發(fā)模式依然沒有解耦,改一個(gè)bug依然會改動(dòng)很多模塊,并不能解決大型項(xiàng)目的問題。
如下圖所示,一開始我們定義的module之間并沒有過多耦合:
然后,隨著項(xiàng)目的不斷迭代,相互調(diào)用的情況會增多,也會增加一些庫的擴(kuò)展和調(diào)用,工程的架構(gòu)可能變?yōu)椋?/p>
可以看出,各種業(yè)務(wù)之間的耦合非常嚴(yán)重,導(dǎo)致代碼非常難以維護(hù),更難以測試,擴(kuò)展和維護(hù)性非常差,這樣的架構(gòu)肯定會被替代。
隨著時(shí)間的推移,出現(xiàn)了組件化、插件化等組織架構(gòu)。
組件化架構(gòu)
這里先提幾個(gè)概念,我們?nèi)粘I(yè)務(wù)需求開發(fā)的組件叫做業(yè)務(wù)組件,如果這個(gè)業(yè)務(wù)需求是可以被普遍復(fù)用的,那么叫做業(yè)務(wù)基礎(chǔ)組件,譬如圖片加載、網(wǎng)絡(luò)請求等框架組件我們稱為基礎(chǔ)組件。搭建所有組件的app組件稱為殼組件/工程。接下來看一張架構(gòu)圖:
實(shí)線表示直接依賴關(guān)系,虛線表示間接依賴。比如殼工程肯定是要依賴業(yè)務(wù)基礎(chǔ)組件、業(yè)務(wù)組件、module_common公共庫的。業(yè)務(wù)組件依賴業(yè)務(wù)基礎(chǔ)組件,但并不是直接依賴,而是通過”下沉接口“來實(shí)現(xiàn)間接調(diào)用。業(yè)務(wù)組件之間的依賴也是間接依賴。最后common組件依賴所有需要的基礎(chǔ)組件,common也屬于基礎(chǔ)組件,它只是統(tǒng)一了基礎(chǔ)組件的版本,同時(shí)也提供了給應(yīng)用提供一些抽象基類,比如BaseActivity、BaseFragment,基礎(chǔ)組件初始化等。
組件化帶來的優(yōu)勢
加快編譯速度:每個(gè)業(yè)務(wù)組件都可以單獨(dú)運(yùn)行調(diào)試,速度提升好幾倍。舉個(gè)例子:video組件單獨(dú)編譯運(yùn)行時(shí)間為3s,因?yàn)榇藭r(shí)AS只會運(yùn)行video組件以及video組件依賴的組件的task,而如果集成編譯時(shí)間為10s,app所引用的所有的組件的task都會執(zhí)行??梢?,效率提升了3倍。
提高協(xié)作效率:每個(gè)組件都有專人維護(hù),不用關(guān)心其他組件是怎么實(shí)現(xiàn)的,只需要暴露對方需要的數(shù)據(jù)。測試也不需要整個(gè)回歸,只需要重點(diǎn)測試修改的組件即可。
功能重用:一次編碼處處復(fù)用,再也不需要了。尤其是基礎(chǔ)組件和業(yè)務(wù)基礎(chǔ)組件,基本上調(diào)用者根據(jù)文檔就可以一鍵集成和使用。
確保了整體技術(shù)方案的統(tǒng)一性,為未來插件化公用一套底層模型做準(zhǔn)備。
前面有提到非大型項(xiàng)目一般不會進(jìn)行組件化,但是就像上面提到的功能重用,這個(gè)優(yōu)勢并不是只能用到大型項(xiàng)目 。我們可以在寫需求或庫時(shí)完全可以擁有組件化思想,把它們單獨(dú)寫成一個(gè)基礎(chǔ)組件或業(yè)務(wù)基礎(chǔ)組件。當(dāng)?shù)诙€(gè)項(xiàng)目來的時(shí)候正好也需要這個(gè)組件,那我們就省去了拆出這個(gè)組件的時(shí)間(因?yàn)閷懶枨蟮臅r(shí)候很可能會造成大量耦合,后續(xù)拆分要花費(fèi)時(shí)間),比如登錄組件,分享組件等等都是可以在一開始就寫成組件的。
組件化需解決的問題
如何解決資源配置沖突問題?
業(yè)務(wù)組件如何實(shí)現(xiàn)單獨(dú)調(diào)試?
業(yè)務(wù)組件間沒有依賴,如何實(shí)現(xiàn)頁面跳轉(zhuǎn)?
業(yè)務(wù)組件間沒有依賴,如何實(shí)現(xiàn)數(shù)據(jù)通信?
業(yè)務(wù)組件間沒有依賴,如何實(shí)現(xiàn)消息通信
殼工程Application生命周期如何下發(fā)?
資源沖突解決
AndroidManifest
每個(gè)module都有一份AndroidManifest文件來記載信息,最終生成一個(gè)App的時(shí)候,只會有一份AndroidManifest來知道APP應(yīng)該去如何配置,Manifest Merge Tools 會將多個(gè)AndroidManifest合成一個(gè),但是又沖突需要解決。
在build/intermediates/manifest_merge_blame_file下會生成一份合并報(bào)告
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.liang.mosic" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="21" ->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml android:targetSdkVersion="30" /> ->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET" /> -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:5-67 -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:22-64 <application -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:6:5-48:19 android:name="com.liang.mosic.ModuleApplication" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:10:9-42 android:allowBackup="true" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:7:9-35 android:appComponentFactory="androidx.core.app.CoreComponentFactory" -->[androidx.core:core:1.5.0] C:\Users\Administrator.gradle\caches\transforms-2\files-2.1\4c9b62de2468f1520f5d232befb24ab8\core-1.5.0\AndroidManifest.xml:24:18-86 android:debuggable="true" android:icon="@mipmap/ic_launcher" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:8:9-43 android:label="@string/app_name" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:9:9-41 android:supportsRtl="true" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:11:9-35 android:testOnly="true" android:theme="@style/AppTheme" > -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:13:9-40 <!-- <activity android:name=".MainActivity"> --> <!-- <intent-filter> --> <!-- <action android:name="android.intent.action.MAIN" /> --> <!-- <category android:name="android.intent.category.LAUNCHER" /> --> <!-- </intent-filter> --> <!-- </activity> --> <activity -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:9-28:20 android:name="com.liang.mosic.AdaviceActivity" -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:19-50 android:theme="@style/AppWelcome" > -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:22:13-46 <intent-filter> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29 <action android:name="android.intent.action.MAIN" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66 <category android:name="android.intent.category.LAUNCHER" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74 </intent-filter> </activity> <activity android:name="com.liang.mosic.ModuleMainActivity" > -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:9-40:20 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:19-53 <!-- <intent-filter> --> <!-- <action android:name="android.intent.action.MAIN" /> --> <!-- <category android:name="android.intent.category.LAUNCHER" /> --> <!-- </intent-filter> --> <intent-filter> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:35:13-39:29 <action android:name="com.liang.main" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:17-60 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:25-57 <category android:name="android.intent.category.DEFAULT" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73 </intent-filter> </activity> <activity android:name="com.liang.mosic.ModuleExampleActivity" > -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:9-47:20 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:19-56 <intent-filter> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:42:13-46:29 <action android:name="com.liang.moduleFragment" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:17-70 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:25-67 <category android:name="android.intent.category.DEFAULT" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73 </intent-filter> </activity> <activity android:name="com.liang.a.MainActivity" > -->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:9-23:20 -->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:19-61 <intent-filter> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29 <action android:name="android.intent.action.MAIN" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66 <category android:name="android.intent.category.LAUNCHER" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74 </intent-filter> </activity> <activity android:name="com.liang.b.BActivity" > -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:9-27:20 -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:19-58 <intent-filter> -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:22:13-26:29 <action android:name="com.liang.b.act" /> -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:17-61 -->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:25-58 <category android:name="android.intent.category.DEFAULT" /> -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76 -->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73 </intent-filter> </activity> </application> </manifest>
最終編譯出的app會將其他所有module的AndroidManifest文件合并,合并規(guī)則為:
- 如果功能module有Application,主module沒有聲明,則使用功能module的Application;
- 如果主module有定義Application,其他module沒有,則使用主module的;
- 如果功能module有多個(gè)自定義Application,解決沖突后使用;
- 如果主module有Application,功能module也有,則解決沖突后,最后編譯的主module的Application會在AndroidManifest中。
如果子module中聲明icon、theme等屬性,會導(dǎo)致合并沖突,需要申明屬性:
xmlns:tools="http://schemas.android.com/tools" tools:replace="android:icon,android:theme"
權(quán)限申明:
在子module中申明的權(quán)限,會集成到主module中,四大組件也是相同的規(guī)則。shareUid只有在主module中申明,才會打包到最終的AndroidManifest中。
獨(dú)立調(diào)試
單工程方案
所謂的單工程方案就是把所有組件都放到一個(gè)工程下,先看一下整體的目錄:
ps:
module_ 開頭表示基礎(chǔ)組件,
fun_ 前綴表示業(yè)務(wù)基礎(chǔ)組件,
biz_前綴表示業(yè)務(wù)組件,
export_前綴表示業(yè)務(wù)組件暴露接口。
單工程利弊分析:
- 利:一個(gè)模塊修改后只需要編譯一下,依賴它的其他模塊就能馬上感知到變化。
- 弊:沒能做到完全的職責(zé)拆分,每個(gè)模塊的開發(fā)者都有修改其他模塊的權(quán)限。
首先在 gradle.properties 文件內(nèi)聲明一個(gè)變量:
// gradle.properties isModule = true
isModule 為 true 時(shí)表示組件可以作為 apk 運(yùn)行起來,false 表示組件只能作為 library。我們根據(jù)需要改變這個(gè)值后同步下gradle即可。
然后在某個(gè) module 的 build.gradle 文件內(nèi)用這個(gè)變量做三個(gè)地方的判斷:
// build.gradle // 區(qū)分是應(yīng)用還是庫 if(isModule.toBoolean()) { apply plugin: 'com.android.application' }else { apply plugin: 'com.android.library' } android { defaultConfig { // 如果是應(yīng)用需要指定application if(isModule.toBoolean()) { applicationId "com.xxx.xxx" } } sourceSets { main { // 應(yīng)用和庫的AndroidManifest文件區(qū)分 if(isModule.toBoolean()) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' }else { manifest.srcFile 'src/main/AndroidManifest.xml' } } } }
由于library是不需要 Application 和啟動(dòng)Activity頁,所以我們要區(qū)分這個(gè)文件,應(yīng)用manifest指定的路徑?jīng)]有特定,隨意找個(gè)路徑創(chuàng)建即可。在應(yīng)用AndroidManifest.xml里我們要設(shè)置啟動(dòng)頁:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sun.biz_home"> <application android:allowBackup="true" android:label="@string/home_app_name" android:supportsRtl="true" android:theme="@style/home_AppTheme"> <activity android:name=".debug.HomeActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
library 的 AndroidManifest.xml 不需要這些:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sun.biz_home"> </manifest>
gradle 依賴 module 的方式主要有兩種:
- implementation: A implementation B,B implementation C, 但 A 不能訪問到 C 的東西。
- api:A api B,B api C,A能訪問到C的東西。
一般來說我們只需要使用 implementation 即可,api 是會造成項(xiàng)目編譯時(shí)間變長,而且會引入該模塊不需要的功能,代碼之間耦合變得嚴(yán)重了。不過 module_common 是統(tǒng)一了基礎(chǔ)組件版本的公共庫,所有組件都應(yīng)需要依賴它并擁有基礎(chǔ)組件的能力,所以基本每個(gè)業(yè)務(wù)組件和業(yè)務(wù)基礎(chǔ)組件都應(yīng)該依賴公共庫:
dependencies { implementation project(':module_common') }
而 common 組件依賴基礎(chǔ)組件應(yīng)該是用 api,因?yàn)榘鸦A(chǔ)組件的能力傳遞給上層業(yè)務(wù)組件:
dependencies { api project(':module_base') api project(':module_util') }
多工程方案
多工程就是每個(gè)組件都是一個(gè)工程,例如創(chuàng)建一個(gè)工程后 app 作為殼組件,它依賴 biz_home 運(yùn)行,因此不需要 isModule 來控制獨(dú)立調(diào)試,它本身就是一個(gè)工程可以獨(dú)立調(diào)試。
多工程的利弊就是和單工程相反的:
- 利:做到職責(zé)完全拆分,其他項(xiàng)目復(fù)用更加方便,直接一行依賴引入。
- 弊:修改后需要上傳到maven倉庫,其他工程再次編譯后才能感知到變化,多了上傳和編譯的時(shí)間。
多工程組件依賴需要用到maven倉庫。把每個(gè)組件的aar上傳到公司內(nèi)網(wǎng)的maven倉庫,然后像這樣去依賴:
implementation 'com.xxx.xxx:module_common:1.0.0'
我們把三方庫統(tǒng)一放到 config.gradle 內(nèi)管理:
ext { dependencies = [ "glide": "com.github.bumptech.glide:glide:4.12.0", "glide-compiler": "com.github.bumptech.glide:compiler:4.12.0", "okhttp3": "com.squareup.okhttp3:okhttp:4.9.0", "retrofit": "com.squareup.retrofit2:retrofit:2.9.0", "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:2.9.0", "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:2.9.0", "rxjava2": "io.reactivex.rxjava2:rxjava:2.2.21", "arouter": "com.alibaba:arouter-api:1.5.1", "arouter-compiler": "com.alibaba:arouter-compiler:1.5.1", // our lib "module_util": "com.sun.module:module_util:1.0.0", "module_common": "com.sun.module:module_common:1.0.0", "module_base": "com.sun.module:module_base:1.0.0", "fun_splash": "com.sun.fun:fun_splash:1.0.0", "fun_share": "com.sun.fun:fun_share:1.0.0", "export_biz_home": "com.sun.export:export_biz_home:1.0.0", "export_biz_me": "com.sun.export:export_biz_me:1.0.0", "export_biz_msg": "com.sun.export:export_biz_msg:1.0.0", "biz_home": "com.sun.biz:biz_home:1.0.0", "biz_me": "com.sun.biz:biz_me:1.0.0", "biz_msg": "com.sun.biz:biz_msg:1.0.0" ] }
這樣方便版本統(tǒng)一管理, 然后在根目錄的 build.gradle 內(nèi)導(dǎo)入:
apply from: 'config.gradle'
最后在各自的模塊引入依賴,比如在 module_common 中這么引入依賴即可。
dependencies { api rootProject.ext.dependencies["arouter"] kapt rootProject.ext.dependencies["arouter-compiler"] api rootProject.ext.dependencies["glide"] api rootProject.ext.dependencies["okhttp3"] api rootProject.ext.dependencies["retrofit"] api rootProject.ext.dependencies["retrofit-converter-gson"] api rootProject.ext.dependencies["retrofit-adapter-rxjava2"] api rootProject.ext.dependencies["rxjava2"] api rootProject.ext.dependencies["module_util"] api rootProject.ext.dependencies["module_base"] }
個(gè)人覺得多工程適合"很大"的工程,每個(gè)業(yè)務(wù)組件可能都需要一個(gè)組開發(fā),類似淘寶這樣的app。但這只是針對業(yè)務(wù)組件來說的,業(yè)務(wù)基礎(chǔ)組件和基礎(chǔ)組件修改的頻率不會很大,最好都是單工程上傳至maven倉庫來使用。本文的例子是為了方便所以把所有組件寫到一起了,最好的方式就是把 fun_ 和 module_ 開頭的組件都拆分成單工程獨(dú)立開發(fā),業(yè)務(wù)組件寫到一個(gè)工程內(nèi)。
頁面跳轉(zhuǎn)
做完組件之間的隔離后,暴露出來最明顯的問題就是頁面跳轉(zhuǎn)和數(shù)據(jù)通信的問題。一般來說,頁面跳轉(zhuǎn)都是顯示startActivity跳轉(zhuǎn),在組件化項(xiàng)目內(nèi)就不適用了,隱式跳轉(zhuǎn)可以用,但每個(gè)Activity都要寫 intent-filter 就顯得有點(diǎn)麻煩,如下所示:
<activity android:name="com.liang.lib_video.videoplayer.VideoActivity"> <intent-filter> <action android:name="com.intent.openVideoActivity"></action> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
Intent intent = new Intent(); intent.setClass("包名","Activity路徑"); intent.setComponent(new ComponentName("包名")); startActivity(intent);
使用上述方式跳轉(zhuǎn)會奔潰,提示在AndroidManifest文件中注冊,這里需要注意的是,setClass/setComponent是APP的包名而不是所在module的包名。 可以參考最終生成的AndroidManifest文件。使用隱式跳轉(zhuǎn)也可以先用resolveActivity進(jìn)行驗(yàn)證。 如果不想要其他應(yīng)用通過隱式打開,需要設(shè)置exported=false。
隱式跳轉(zhuǎn)是整個(gè)系統(tǒng)都能接收到,會相對想好性能,所以最好的方式還是用路由框架。
實(shí)際上市面已經(jīng)有比較成熟的路由框架專門就是為了組件化而生的,比如美團(tuán)的WMRouter,阿里的ARouter等,本例使用 ARouter 框架,看下ARouter頁面跳轉(zhuǎn)的基本操作。
首先肯定是引入依賴,以 module_common 引入ARouter舉例,build.gradle 應(yīng)該添加:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { api rootProject.ext.dependencies["arouter"] kapt rootProject.ext.dependencies["arouter-compiler"] }
kapt注解依賴沒有辦法傳遞,所以我們不可避免得需要在每個(gè)模塊都聲明這些配置,除了 api rootProject.ext.dependencies["arouter"] 這行。然后需要全局注冊 ARouter,我是在 module_common 統(tǒng)一注冊的。
class AppCommon: BaseApp{ override fun onCreate(application: Application) { MLog.d(TAG, "BaseApp AppCommon init") initARouter(application) } private fun initARouter(application: Application) { if(BuildConfig.DEBUG) { ARouter.openLog() ARouter.openDebug() } ARouter.init(application) } }
接著我們在 module_common 模塊內(nèi)聲明一個(gè)路由表用作統(tǒng)一管理路徑。
// RouterPath.kt class RouterPath { companion object { const val APP_MAIN = "/app/MainActivity" const val HOME_FRAGMENT = "/home/HomeFragment" const val MSG_FRAGMENT = "/msg/MsgFragment" const val ME_FRAGMENT = "/me/MeFragment" const val MSG_PROVIDER = "/msg/MsgProviderImpl" } }
然后在MainActivity類文件上進(jìn)行注解:
@Route(path = RouterPath.APP_MAIN) class MainActivity : AppCompatActivity() { }
任意模塊只需要調(diào)用 ARouter.getInstance().build(RouterPath.APP_MAIN).navigation() 即可實(shí)現(xiàn)跳轉(zhuǎn)。如果我們要加上數(shù)據(jù)傳遞也很方便:
ARouter.getInstance().build(RouterPath.APP_MAIN) .withString("key", "value") .withObject("key1", obj) .navigation()
然后在MainActivity使用依賴注入接受數(shù)據(jù):
class MainActivity : AppCompatActivity() { @Autowired String key = "" }
Arouter 實(shí)現(xiàn)組件間方法調(diào)用
在 export_biz_msg 組件下聲明 IMsgProvider,此接口必須實(shí)現(xiàn) IProvider 接口:
interface IMsgProvider: IProvider { fun onCountFromHome(count: Int = 1) }
然后在 biz_msg 組件里實(shí)現(xiàn)這個(gè)接口:
@Route(path = RouterPath.MSG_PROVIDER) class MsgProviderImpl: IMsgProvider { override fun onCountFromHome(count: Int) { // 這里只是對數(shù)據(jù)進(jìn)行分發(fā),有監(jiān)聽計(jì)數(shù)的對象會收到 MsgCount.instance.addCount(count) } override fun init(context: Context?) { // 對象被初始化時(shí)調(diào)用 } }
在 biz_home 首頁組件中發(fā)送計(jì)數(shù):
val provider = ARouter.getInstance().build(RouterPath.MSG_PROVIDER).navigation() as IMsgProvider provider.onCountFromHome(count)
可以看到其實(shí)和頁面跳轉(zhuǎn)的方式基本雷同,包括獲取 Fragment 實(shí)例的方式也是這種。ARouter把所有通信的方式都用一種api實(shí)現(xiàn),讓使用者上手非常容易。
組件化的消息通信方式選擇
廣播
作為Android中四大組件之一,Broadcast的職責(zé)是用于Android系統(tǒng)通信,但是普通的廣播是全局廣播,會造成安全泄露以及效率問題,如果只是在應(yīng)用內(nèi)部通知,可以使用更為高效的LocalBroadCast,相對于全局廣播,本地廣播只會在APp內(nèi)部傳播,不會造成隱私泄露,同時(shí)無法接受其他應(yīng)用發(fā)送的廣播,相對于全局廣播來說更加高效。
事件總線
由于系統(tǒng)級別的廣播傳遞比較耗時(shí),消息通信科使用通過記錄對象、使用監(jiān)聽者模式實(shí)現(xiàn)的事件總線框架,比如EventBus、LivaData等。
通過將消息的公用部分 ,如自定義消息的bean放入到baseMOdule下的單獨(dú)模塊來實(shí)現(xiàn)組件間消息的傳遞。組件化的數(shù)據(jù)庫存儲和消息通信的實(shí)現(xiàn)方式大同小異,都是將公用的東西放入到baseModule,如果內(nèi)容比較多或者對于職責(zé)界限劃分要求高的話可在base下新建一個(gè)DataBaseModule
Application生命周期分發(fā)
當(dāng) app 殼工程啟動(dòng)Application初始化時(shí)要通知到其他組件初始化一些功能。這里提供一個(gè)簡單的方式。
首先我們在 module_common 公共庫內(nèi)聲明一個(gè)接口 BaseApp:
public interface BaseAppInit { boolean onInitCreate(Application application); boolean onInitTerminal(Application application); }
然后每個(gè)組件都要?jiǎng)?chuàng)建一個(gè) App 類實(shí)現(xiàn)此接口,比如在某個(gè)業(yè)務(wù)組件:
public class AudioInit implements BaseAppInit { @Override public boolean onInitCreate(Application application) { return false; } @Override public boolean onInitTerminal(Application application) { return false; } }
剩下最后一步就是從 app 殼工程分發(fā) application 的生命周期了,這里用到反射技術(shù):
val moduleInitArr = arrayOf( "com.liang.lib_audio.app.AudioInit", "com.liang.lib_audio.app.VideoInit", "com.liang.lib_audio.app.LoginInit", ) class App: Application() { override fun onCreate() { super.onCreate() initModuleApp(this) } private fun initModuleApp(application: Application) { try { for(appName in moduleInitArr) { val clazz = Class.forName(appName) val module = clazz.getConstructor().newInstance() as BaseApp module.onCreate(application) } }catch (e: Exception) { e.printStackTrace() } } }
我們只需要知道的每個(gè)實(shí)現(xiàn) BaseApp 接口的類的全限定名并寫到moduleInitArr數(shù)組里,然后通過反射獲取 Class 對象從而獲取構(gòu)造函數(shù)創(chuàng)建實(shí)體對象,最后調(diào)用 BaseApp 的 onCreate 方法將 application 傳入,每個(gè)Application生命周期的方法都可以通過這種方式傳遞。由于反射會消耗一定的性能,這個(gè)操作可以放在子線程,然后線程間通信。 當(dāng)然,在每個(gè)module定義相對應(yīng)的初始化方法,然后主module 調(diào)用也可以實(shí)現(xiàn)初始化,此處使用反射是為了最大程度的解耦。
以上就是Android開發(fā)組件化架構(gòu)設(shè)計(jì)原理到實(shí)戰(zhàn)的詳細(xì)內(nèi)容,更多關(guān)于Android組件化架構(gòu)設(shè)計(jì)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)雙層ViewPager嵌套
這篇文章主要介紹了Android實(shí)現(xiàn)雙層ViewPager嵌套,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04android實(shí)現(xiàn)滾動(dòng)文本效果
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)滾動(dòng)文本效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Android中RecyclerView實(shí)現(xiàn)多級折疊列表效果(TreeRecyclerView)
RecyclerView出現(xiàn)已經(jīng)有一段時(shí)間了,相信大家肯定不陌生了,下面這篇文章主要給大家介紹了Android中RecyclerView實(shí)現(xiàn)多級折疊列表效果(TreeRecyclerView)的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考下。2017-05-05android實(shí)現(xiàn)藍(lán)牙文件發(fā)送的實(shí)例代碼,支持多種機(jī)型
這篇文章主要介紹了android實(shí)現(xiàn)藍(lán)牙文件發(fā)送的實(shí)例代碼,有需要的朋友可以參考一下2014-01-01Android自定義SurfaceView實(shí)現(xiàn)畫板功能
這篇文章主要為大家詳細(xì)介紹了Android自定義SurfaceView實(shí)現(xiàn)畫板功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07基于Fedora14下自帶jdk1.6版本 安裝jdk1.7不識別的解決方法
本篇文章是對Fedora14下自帶jdk1.6版本,安裝jdk1.7不識別的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05