Android元件化、模組化實現
前言
移動端平臺不斷髮展,不斷迭代更新,APP軟體越來越複雜和龐大,維護和更新亦是如此。為了解決這些問題,降低軟體的複雜性和耦合度,同時提高開發效率,模組化在移動端就變得勢在必行。
模組化理解
模組化是指解決一個複雜問題時自頂向下逐層把系統劃分成若干模組的過程。每個模組完成一個特定的子功能,所有的模組按某種方法組裝起來,成為一個整體,完成整個系統所要求的功能。
通過以下類比可以更好地理解什麼是模組化:
我們可以把軟體看做是一輛汽車,開發一款軟體的過程就是生產一輛汽車的過程。一輛汽車由車架、發動機、變數箱、車輪等一系列模組組成;同樣,一款大型商業軟體也是由各個不同的模組組成的。
汽車的這些模組是由不同的工廠生產的,一輛 BMW 的發動機可能是由位於德國的工廠生產的,它的自動變數箱可能是 Jatco(世界三大變速箱廠商之一)位於日本的工廠生產的,車輪可能是中國的工廠生產的,最後交給華晨寶馬的工廠統一組裝成一輛完整的汽車。這就類似於我們在軟體工程領域裡說的多團隊並行開發,最後將各個團隊開發的模組統一打包成我們可使用的 App 。
一款發動機、一款變數箱都不可能只應用於一個車型,比如同一款 Jatco 的 6AT 自動變速箱既可能被安裝在 BMW 的車型上,也可能被安裝在 Mazda 的車型上。這就如同軟體開發領域裡的模組重用。
到了冬天,特別是在北方我們可能需要開著車走雪路,為了安全起見往往我們會將汽車的公路胎升級為雪地胎;輪胎可以很輕易的更換,這就是我們在軟體開發領域談到的低耦合。一個模組的升級替換不會影響到其它模組,也不會受其它模組的限制;同時這也類似於我們在軟體開發領域提到的可插拔。
模組化優缺點
優點
- 架構靈活,焦點分離
- 耦合低,模組間可自由組合、分解
- 方便單個模組功能除錯、升級、測試,提升開發效率
- 多人協作只負責單獨模組,互不干擾
缺點
- 系統分層,呼叫鏈會很長
- 模組間傳送訊息對比較損耗效能
模組化分層
元件和模組區別定義
- 元件:指的是單一的功能元件,地圖元件、支付元件、分享元件等功能;
- 模組:指的是獨立的業務模組,如首頁模組、聊天模組、播放模組等,模組相對於元件來說粒度更大。
整個專案分為四層,從下至上分別是:
(1)宿主層:不做具體的專案功能實現,只負責整合業務模組,組裝成一個完整的APP;
(2)業務模組層:將專案的每個大功能模組拆分成的一個一個單獨的module,可獨立執行,同產品的不同專案也可複用;
(3)業務元件層:用於業務模組間呼叫,例如支付元件 、地圖元件、分享元件等等;
(4)基礎元件層:基礎元件層,與業務無關,與專案也無關,所有專案都可以全部複用,包含了各種開源庫以及和業務無關的各種自研工具庫;
模組化分層,其實就是將業務模組層的各個功能業務拆分層獨立的業務模組;進行模組化的第一步就是業務模組劃分,劃分的粒度需要根據專案情況進行合理把控,這就需要對業務和專案有較為透徹的理解。
模組化過程
1、每個單獨的Module 都可以單獨作為Application編譯成 APK執行,同時也可以作為依賴包Library來整體編譯打包。於是,需要在每個Module下的 build.gradle
中加入如下程式碼:
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
isModule作為開關去控制是否為Application後者Library,只需要在專案gradle.properties
檔案中加入如下配置:
isModule=false
2、作為Application時清單檔案需要設定Application屬性及啟動Activity等,而Library則簡單很多,因此就需要兩套不同的AndroidManifest.xml
。同樣,只要在每個Module下的 build.gradle
的android配置中加入如下設定:
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除debug資料夾中的所有Java檔案
java {
exclude 'debug/**'
}
}
}
}
保持原先拷貝一份AndroidManifest.xml
到新建的debug目錄下即可,如圖所示:
main目錄下作為Library清單檔案:
<application>
<activity android:name=".BottomAlignmentActivity" />
</application>
debug目錄下作為Application中清單檔案:
<application
android:icon="@drawable/ic_author"
android:label="@string/advancedtextview_app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".BottomAlignmentActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
3、宿主APP在Application和Library模式下,進行不同依賴配置,:
if (!isModule.toBoolean()) {
implementation project(path: ':advancedtextview')
}else{
implementation project(":libbase")
}
4、模組間跳轉
宿主工程中依賴的庫是可以直接引用的,通過startActivity
跳轉,但元件之間不可以依賴。因此,當常規業務模組之間需要跳轉引用,改如何處理呢?
- 隱式Intent,要跳轉的活動在
Manifest.xml
中宣告匹配規則,然後呼叫
Intent intent = new Intent(Intent.ACTION_VIEW, "<scheme>://<host>:<port>/<path>");
startActivity(intent);
- 利用反射
Class clazz=Class.fromName("com.nianlun.expample.MainActivity");
startActivity(this,clazz);
- 使用路由,這裡推薦阿里的ARouter
一個用於幫助 Android App 進行元件化改造的框架 —— 支援模組間的路由、通訊、解耦。
Arouter使用
-
新增依賴和配置
android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } } dependencies { api 'com.alibaba:arouter-api:x.x.x' annotationProcessor 'com.alibaba:arouter-compiler:x.x.x' ... }
-
添加註解
// 在支援路由的頁面上添加註解(必選) // 這裡的路徑需要注意的是至少需要有兩級,/xx/xx @Route(path = "/test/activity") public class YourActivity extend Activity { ... }
-
初始化SDK
if (isDebug()) { // 這兩行必須寫在init之前,否則這些配置在init過程中將無效 ARouter.openLog(); // 列印日誌 ARouter.openDebug(); // 開啟除錯模式(如果在InstantRun模式下執行,必須開啟除錯模式!線上版本需要關閉,否則有安全風險) } ARouter.init(mApplication); // 儘可能早,推薦在Application中初始化
-
發起路由操作
// 1. 應用內簡單的跳轉(通過URL跳轉在'進階用法'中) ARouter.getInstance().build("/test/activity").navigation(); // 2. 跳轉並攜帶引數 ARouter.getInstance().build("/test/1") .withLong("key1", 666L) .withString("key3", "888") .withObject("key4", new Test("Jack", "Rose")) .navigation();
更多進階用法,可以跳轉官方GitHub網站進行文件檢視:https://github.com/alibaba/ARouter/blob/master/README_CN.md
問題解決
資源名衝突的問題,可以通過在 build.gradle
定義字首的方式解決:
defaultConfig {
...
resourcePrefix "module_name_"
...
}
參考資料:Android 模組化探索與實踐
以上就是模組化的基本過程和實現,具體專案中我們還是要理清業務之間的關係,進行具體劃分,提取公共依賴,剔除冗餘程式碼,逐步進行重構。
借用以前的程式碼例子,模組化處理了一下,採用Arouter進行跳轉,具體可以檢視我的Github檢視,地址如下
https://github.com/MickJson/DevelopmentRecord
歡迎點選查閱及Star,我也會繼續補充其它有用的知識及例子在專案上。
歡迎點贊/評論,你們的贊同和鼓勵是我寫作的最大動力!