關於Android的組件化,相信你們並不陌生,網上談論組件化的文章,多如過江之鯽,然而一篇基於MVVM模式的組件化方案卻不多。結合自身的調研和探索,在此分享一篇基於MVVMHabit框架的一套Android-Databinding組件化開發方案。文章寫的比較簡單基礎,沒有大篇幅單向技術的討論點,目的是讓學習了此方案的開發人員均可以快速上手搭建MVVM組件化項目。html
原文地址: github.com/goldze/MVVM…前端
想必熟悉前端的朋友對MVVM很是瞭解,這種模式在目前web前端火的一塌糊塗,好比vue、angular、react都是採用MVVM設計模式實現的前端框架。而在Android開發中,MVVM並非惟一的架構模式,最經常使用的多是MVC模式(一般不是理想的實現)。比較流行的是MVP,它在某種程度上與MVVM模式很是類似。不過MVVM在MVP的基礎上更進一步的提升了開發效率,擁有了數據綁定的能力。說到Android MVVM,很天然的聯想到谷歌出的Databinding,它提供了xml與java的完美綁定,就像html與js的綁定同樣。雖然Android端的MVVM還不是很火,但我相信它是一種趨勢。趁着web前端MVVM的熱度,移動前端也應該崛起了。vue
代碼是死的,產品是活的。在平常開發中,各類各樣頻繁變更的需求,給開發上帶來了不小的麻煩。爲了儘可能把代碼寫「活」,因此出現了設計模式。但光有設計模式,仍是很難知足產品BT的需求。java
對於簡單的小項目,大多都採用的是單一工程,獨立開發。因爲項目不大,編譯速度及維護成本這些也在接受範圍以內。而對於作好一個App產品,這種多人合做、單一工程的App架構勢必會影響開發效率,增長項目的維護成本。每一個開發者都要熟悉如此之多的代碼,將很難進行多人協做開發,並且Android項目在編譯代碼的時候電腦會很是卡,又由於單一工程下代碼耦合嚴重,每修改一處代碼後都要從新編譯打包測試,致使很是耗時,最重要的是這樣的代碼想要作單元測試根本無從下手,因此必需要有更靈活的架構代替過去單一的工程架構。react
使用組件化方案架構,高內聚,低耦合,代碼邊界清晰,每個組件均可以拆分出來獨立運行。全部組件寄託於宿主App,加載分離的各個組件,各自編譯本身的模塊,有利於多人團隊協做開發。android
光說理論沒用,來點實際的東西,這裏要提兩個重要的框架。git
MVVMHabit + ARouter:MVVM模式 + 組件化方案,前者是設計模式,後者是方案架構,二者並用,相得益彰。有這兩個框架做支撐,事半功倍,可快速開發組件化應用。github
先把工程中最基本的架子建立好,再一步步將其關聯起來web
搭建組件化項目與單一工程項目同樣,先經過Android Studio建立一個常規項目。windows
File->New->New Project...
建立的這個項目將其定義爲「 宿主 」(大多數人都是這種叫法),也能夠叫空殼項目。它沒有layout,沒有activity,它的職責是將分工開發的組件合而爲一,打包成一個可用的Apk。
在宿主工程中,主要包含兩個東西,一個是AndroidManifest.xml:配置application、啓動頁面等;另外一個是build.gradle:負責配置構建編譯/打包參數,依賴子模塊。
所謂的組件,其實也就是一個Module,不過這個Module有點特殊,在合併打包的時候它是一個library:apply plugin: ‘com.android.library’,在獨立編譯運行的時候,它是一個application:apply plugin: ‘com.android.application’。
File->New->New Module->Android Library...
通常能夠取名爲module-xxx(組件名)
除了業務組件以外,還須要建立兩個基礎Library,library-base 和 library-res。
library-base:存放一些公共方法、公共常量、組件通訊的契約類等。上層被全部組件依賴,下層依賴公共資源庫、圖片選擇庫、路由庫等通用庫,經過它,避免了組件直接依賴各類通用庫,承上啓下,做爲整個組件化的核心庫。
library-res:爲了緩解base庫的壓力,專門分離出一個公共資源庫,被base庫所依賴,主要存放與res相關的公共數據,好比圖片、style、anim、color等。
還須要準備兩個第三方的框架,即前面說的 MVVMHabit 和 ARouter,可以使用遠程依賴。
MVVMHabit:
allprojects {
repositories {
...
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
複製代碼
dependencies {
...
implementation 'com.github.goldze:MVVMHabit:?'
}
複製代碼
ARouter:
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
複製代碼
dependencies {
api 'com.alibaba:arouter-api:?'
annotationProcessor 'com.alibaba:arouter-compiler:?'
}
複製代碼
組件化實際上是一個 分離--組合 的過程,分離是分離產品原型,組合是組合代碼模塊。拿到需求後,必定不要急着開幹,首先將產品原型分離成一個個子原型,分工開發後,將編寫完成的子業務模塊又打包組合成一個完整的Apk。
最多見的應屬這種底部幾個tab的設計。
經過組件化,能夠按照業務大體將項目拆分爲:首頁模塊、工做模塊、消息模塊、用戶模塊,固然還能夠再分細一點,好比用戶模塊再分離一個身份驗證模塊出來。拆分的越細,複用起來就越方便。
那麼在上面2.1.2節建立組件時,則建立如下幾個組件Module:module-home、module-work、module-msg、module-user、module-sign。
gradle是組件化的基石,想搭建好組件化項目,gradle知識必定要紮實(Android已經留下了gradle的烙印)。
項目建立好後,須要將他們串聯起來,組合在一塊兒。依賴關係以下圖所示:
宿主依賴業務組件
dependencies {
//主業務模塊
implementation project(':module-main')
//身份驗證模塊
implementation project(':module-sign')
//首頁模塊
implementation project(':module-home')
//工做模塊
implementation project(':module-work')
//消息模塊
implementation project(':module-msg')
//用戶模塊
implementation project(':module-user')
}
複製代碼
業務組件依賴library-base
dependencies {
//組件依賴基礎庫
api project(':library-base')
//按需依賴第三方組件
}
複製代碼
library-base依賴公共庫
dependencies {
//support相關庫
api rootProject.ext.support["design"]
api rootProject.ext.support["appcompat-v7"]
//library-res
api project(':library-res')
//MVVMHabit框架
api rootProject.ext.dependencies.MVVMHabit
//ARouter框架
api rootProject.ext.dependencies["arouter-api"]
//其餘公共庫,例如圖片選擇、分享、推送等
}
複製代碼
Android MVVM模式離不開DataBinding,每一個組件中都須要開啓,包括宿主App
android {
//開啓DataBinding
dataBinding {
enabled true
}
}
複製代碼
須要一個全局變量來控制當前運行的工程是隔離狀態仍是合併狀態。在gradle.properties中定義:
isBuildModule=false
複製代碼
isBuildModule 爲 true 時可使每一個組件獨立運行,false 則能夠將全部組件集成到宿主 App 中。
在組件的build.gradle中動態切換library與application
if (isBuildModule.toBoolean()) {
//做爲獨立App應用運行
apply plugin: 'com.android.application'
} else {
//做爲組件運行
apply plugin: 'com.android.library'
}
複製代碼
當 isBuildModule 爲 true 時,它是一個application,擁有本身的包名
android {
defaultConfig {
//若是是獨立模塊,則使用當前組件的包名
if (isBuildModule.toBoolean()) {
applicationId 組件的包名
}
}
}
複製代碼
組件在本身的AndroidManifest.xml各自配置,application標籤無需添加屬性,也不須要指定activity的intent-filter。當合並打包時,gradle會將每一個組件的AndroidManifest合併到宿主App中。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.goldze.main">
<application>
...
</application>
</manifest>
複製代碼
組件獨立運行時,就須要單獨的一個AndroidManifest.xml做爲調試用。能夠在src/main文件夾下建立一個alone/AndroidManifest.xml。配置application標籤屬性,並指定啓動的activity。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.goldze.main">
<application ... >
<activity ... >
<intent-filter>
...
</intent-filter>
</activity>
</application>
</manifest>
複製代碼
並在build.gradle中配置
android {
sourceSets {
main {
...
if (isBuildModule.toBoolean()) {
//獨立運行
manifest.srcFile 'src/main/alone/AndroidManifest.xml'
} else {
//合併到宿主
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
//正式版本時,排除alone文件夾下全部調試文件
exclude 'src/main/alone/*'
}
}
}
}
}
複製代碼
在組件的build.gradle配置統一資源前綴
android {
//統一資源前綴,規範資源引用
resourcePrefix "組件名_"
}
複製代碼
能夠將每一個組件的build.gradle公共部分抽取出一個module.build.gradle
if (isBuildModule.toBoolean()) {
//做爲獨立App應用運行
apply plugin: 'com.android.application'
} else {
//做爲組件運行
apply plugin: 'com.android.library'
}
android {
...
defaultConfig {
...
//阿里路由框架配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
sourceSets {
main {
if (isBuildModule.toBoolean()) {
//獨立運行
manifest.srcFile 'src/main/alone/AndroidManifest.xml'
} else {
//合併到宿主
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
//正式版本時,排除alone文件夾下全部調試文件
exclude 'src/main/alone/*'
}
}
}
}
buildTypes {
...
}
dataBinding {
enabled true
}
}
複製代碼
組件中引入module.build.gradle便可
apply from: "../module.build.gradle"
android {
defaultConfig {
//若是是獨立模塊,則使用當前組件的包名
if (isBuildModule.toBoolean()) {
applicationId 組件的包名
}
}
//統一資源前綴,規範資源引用
resourcePrefix "組件名_"
}
dependencies {
...
}
複製代碼
運行效果以下:
到此爲止,一個最基本的組件化工程搭建完畢。
組件在獨立運行時,也就是debug期,有單獨的manifest,固然也就能夠指定Application類進行初始化。那麼當組件進行合併的時,Application只能有一個,而且存在宿主App中,組件該如何進行初始化?
反射是一種解決組件初始化的方法。
在library-base下定義一個 ModuleLifecycleConfig 單例類,主要包含兩個公共方法:initModuleAhead(先初始化)、initModuleLow(後初始化)。
爲什麼這裏要定義兩個初始化方法?
組件多了,一定會涉及到初始化的前後順序問題,組件中依賴的第三方庫,有些庫須要儘早初始化,有些能夠稍晚一些。好比ARouter的init方法,官方要求儘量早,那麼就能夠寫在library-base初始化類的onInitAhead中,優先初始化。
@Override
public boolean onInitAhead(Application application) {
KLog.init(true);
//初始化阿里路由框架
if (BuildConfig.DEBUG) {
ARouter.openLog(); // 打印日誌
ARouter.openDebug(); // 開啓調試模式(若是在InstantRun模式下運行,必須開啓調試模式!線上版本須要關閉,不然有安全風險)
}
ARouter.init(application); // 儘量早,推薦在Application中初始化
return false;
}
複製代碼
再定義一個組件生命週期管理類 ModuleLifecycleReflexs ,在這裏註冊組件初始化的類名全路徑,經過反射動態調用各個組件的初始化方法。
注意:組件中初始化的Module類不能被混淆
定義一個 IModuleInit 接口,動態配置Application,須要初始化的組件實現該接口,統一在宿主app的Application中初始化
public interface IModuleInit {
//初始化優先的
boolean onInitAhead(Application application);
//初始化靠後的
boolean onInitLow(Application application);
}
複製代碼
反射類和接口都有了,那麼在各自的組件中建立一個初始化類,實現IModuleInit接口。最後在宿主的Application中調用初始化方法
@Override
public void onCreate() {
super.onCreate();
//初始化組件(靠前)
ModuleLifecycleConfig.getInstance().initModuleAhead(this);
//....
//初始化組件(靠後)
ModuleLifecycleConfig.getInstance().initModuleLow(this);
}
複製代碼
最後即實現組件的初始化效果
小優化: 當組件獨立運行時,宿主App不會執行onCreate方法,可是組件業務又須要初始化單獨調試。常規作法是組件中單獨定義Application,但這樣每一個組件都須要建立一個Application,比較繁瑣。咱們有了上述的初始化方法,能夠在 library-base 中定義一個 DebugApplication ,debug包下的代碼不參與編譯,僅做爲獨立模塊運行時初始化數據。最後記得在組件的調試版alone/AndroidManifest下指定爲base中的 DebugApplication。
組件間是徹底無耦合的存在,可是在實際開發中確定會存在業務交叉的狀況,該如何實現無聯繫的組件間通訊呢?
ARouter 之因此做爲整個組件化的核心,是由於它擁有強大的路由機制。ARouter在library-base中依賴,全部組件又依賴於library-base,因此它能夠看做爲組件間通訊的橋樑。
在組件A中跳轉到組件B頁面:
ARouter.getInstance()
.build(router_url)
.withString(key, value)
.navigation();
複製代碼
在組件B頁面中接收傳過來的參數:
@Autowired(name = key)
String value;
複製代碼
更多ARouter用法:github.com/alibaba/ARo…
MVVMHabit 中提供了RxBus,可做爲全局事件的通訊工具。
當組件B頁面須要回傳數據給組件A時,能夠調用:
_Login _login = new _Login();
RxBus.getDefault().post(_login);
複製代碼
在組件A中註冊接收(註冊在調用以前完成):
subscribe = RxBus.getDefault().toObservable(_Login.class)
.subscribe(new Consumer<_Login>() {
@Override
public void accept(_Login l) throws Exception {
//登陸成功後從新刷新數據
initData();
//解除註冊
RxSubscriptions.remove(subscribe);
}
});
RxSubscriptions.add(subscribe);
複製代碼
library-base 有兩個主要做用:一是依賴通用基礎jar或第三方框架,二是存放一些公共的靜態屬性和方法。下面列舉一些基礎通用類的約定規範。
在base的config包下面,統一存放全局的配置文件,好比組件生命週期初始化類:ModuleLifecycleConfig、ModuleLifecycleReflexs,網絡ROOT_URL,SD卡文件讀寫目錄等。
RxBus組件通訊,須要通過base層,統一規範。那麼能夠在contract包下面定義RxBus的契約類,寫好註釋,便於其餘組件開發人員使用。
主要存放全局的Key,好比 IntentKeyGlobal: 存放組件間頁面跳轉傳參的Key名稱; SPKeyGlobal: 全局SharedPreferences Key 統一存放在這裏。單個組件中內部的key能夠另外在單獨組件中定義。
ARouter 路由@Route註解中Path能夠單獨抽取一個或者兩個RouterPath類出來,好比定義一個RouterActivityPath:
public class RouterActivityPath {
/** * 主業務組件 */
public static class Main {
private static final String MAIN = "/main";
/*主業務界面*/
public static final String PAGER_MAIN = MAIN +"/Main";
}
複製代碼
Activity的路由路徑統一在此類中定義,並使用靜態內部類分塊定義各個組件中的路徑路由。
仍是得總結一下。
項目組件化,就比如製造業,生活中的絕大多數工業產品。好比汽車,由發動機、輪子、引擎等各個重要零件拼裝而成。一樣,咱們的app也是由各個組件並聯起來,造成一個完整可執行的軟件。它的精髓就是這麼3點:獨立、完整、自由組合。 並且組件化甚至都不算是人類的發明。即便放在天然界,這也是早已存在的模式。想一想咱們人體多麼複雜,絕對不亞於windows操做系統。但除去幾個很是重要的數器官以外,大多部分損壞或缺失,咱們都能活下來。這不得不說是組件化的奇蹟。
寫軟件必定要注重架構,不要等到代碼越寫越爛,越爛越寫,最後連本身都看不下去了纔想到去重構。組件化是一個很好隔離每一個業務模塊的方案,即便其中一個組件出了問題,也不用像單一工程那樣總體地去調試。配合MVVM設計模式,使得咱們工做中的具體項目變得更輕、好組裝、編譯構建更快,不只提升工做效率,同時自我對移動應用開發認知有進一步的提高。組件化框架具備通用性,特別適用於業務模塊迭代多,量大的大中型項目,是一個很好的解決方案。
Android架構的演進,由 模塊化 到 組件化 再到 插件化。咱們在組件化開發的道路上,儘量的完善組件開發規範,豐富組件功能庫,有一些粒度大的業務組件能夠進一步的細化,對組件功能進行更單一的內聚,同時基於現有的組件化框架,便於過分在將來打造插件化框架。
QQ交流羣:84692105
若是以爲這個方案不錯的話,麻煩點個 star,你的支持則是我前進的動力!