Android MVVM組件化架構方案

MVVMHabitComponent

關於Android的組件化,相信你們並不陌生,網上談論組件化的文章,多如過江之鯽,然而一篇基於MVVM模式的組件化方案卻不多。結合自身的調研和探索,在此分享一篇基於MVVMHabit框架的一套Android-Databinding組件化開發方案。文章寫的比較簡單基礎,沒有大篇幅單向技術的討論點,目的是讓學習了此方案的開發人員均可以快速上手搭建MVVM組件化項目。html

MVVMHabit-Family

原文地址: github.com/goldze/MVVM…前端

總體架構

一、淺談

1.一、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

1.二、組件化開發

代碼是死的,產品是活的。在平常開發中,各類各樣頻繁變更的需求,給開發上帶來了不小的麻煩。爲了儘可能把代碼寫「活」,因此出現了設計模式。但光有設計模式,仍是很難知足產品BT的需求。java

對於簡單的小項目,大多都採用的是單一工程,獨立開發。因爲項目不大,編譯速度及維護成本這些也在接受範圍以內。而對於作好一個App產品,這種多人合做、單一工程的App架構勢必會影響開發效率,增長項目的維護成本。每一個開發者都要熟悉如此之多的代碼,將很難進行多人協做開發,並且Android項目在編譯代碼的時候電腦會很是卡,又由於單一工程下代碼耦合嚴重,每修改一處代碼後都要從新編譯打包測試,致使很是耗時,最重要的是這樣的代碼想要作單元測試根本無從下手,因此必需要有更靈活的架構代替過去單一的工程架構。react

使用組件化方案架構,高內聚,低耦合,代碼邊界清晰,每個組件均可以拆分出來獨立運行。全部組件寄託於宿主App,加載分離的各個組件,各自編譯本身的模塊,有利於多人團隊協做開發。android

1.三、MVVM模式 + 組件化

光說理論沒用,來點實際的東西,這裏要提兩個重要的框架。git

  • MVVMHabit:基於谷歌最新AAC架構,MVVM設計模式的一套快速開發庫,整合Okhttp+RxJava+Retrofit+Glide等主流模塊,知足平常開發需求。使用該框架能夠快速開發一個高質量、易維護的Android應用。
  • ARouter:阿里出的一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通訊、解耦。

MVVMHabit + ARouter:MVVM模式 + 組件化方案,前者是設計模式,後者是方案架構,二者並用,相得益彰。有這兩個框架做支撐,事半功倍,可快速開發組件化應用。github

二、項目搭建

2.一、建立項目

先把工程中最基本的架子建立好,再一步步將其關聯起來web

2.1.一、建立宿主

搭建組件化項目與單一工程項目同樣,先經過Android Studio建立一個常規項目。windows

File->New->New Project...

建立的這個項目將其定義爲「 宿主 」(大多數人都是這種叫法),也能夠叫空殼項目。它沒有layout,沒有activity,它的職責是將分工開發的組件合而爲一,打包成一個可用的Apk。

在宿主工程中,主要包含兩個東西,一個是AndroidManifest.xml:配置application、啓動頁面等;另外一個是build.gradle:負責配置構建編譯/打包參數,依賴子模塊。

2.1.二、建立組件

所謂的組件,其實也就是一個Module,不過這個Module有點特殊,在合併打包的時候它是一個library:apply plugin: ‘com.android.library’,在獨立編譯運行的時候,它是一個application:apply plugin: ‘com.android.application’

File->New->New Module->Android Library...

通常能夠取名爲module-xxx(組件名)

2.1.三、建立Library

除了業務組件以外,還須要建立兩個基礎Library,library-baselibrary-res

  • library-base:存放一些公共方法、公共常量、組件通訊的契約類等。上層被全部組件依賴,下層依賴公共資源庫、圖片選擇庫、路由庫等通用庫,經過它,避免了組件直接依賴各類通用庫,承上啓下,做爲整個組件化的核心庫。

  • library-res:爲了緩解base庫的壓力,專門分離出一個公共資源庫,被base庫所依賴,主要存放與res相關的公共數據,好比圖片、style、anim、color等。

2.1.四、第三方框架準備

還須要準備兩個第三方的框架,即前面說的 MVVMHabitARouter,可以使用遠程依賴。

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:?'
}
複製代碼

2.二、組件分離

組件化實際上是一個 分離--組合 的過程,分離是分離產品原型,組合是組合代碼模塊。拿到需求後,必定不要急着開幹,首先將產品原型分離成一個個子原型,分工開發後,將編寫完成的子業務模塊又打包組合成一個完整的Apk。

最多見的應屬這種底部幾個tab的設計。

經過組件化,能夠按照業務大體將項目拆分爲:首頁模塊工做模塊消息模塊用戶模塊,固然還能夠再分細一點,好比用戶模塊再分離一個身份驗證模塊出來。拆分的越細,複用起來就越方便。

那麼在上面2.1.2節建立組件時,則建立如下幾個組件Module:module-homemodule-workmodule-msgmodule-usermodule-sign

2.三、組件配置

gradle是組件化的基石,想搭建好組件化項目,gradle知識必定要紮實(Android已經留下了gradle的烙印)。

2.3.一、依賴關係

項目建立好後,須要將他們串聯起來,組合在一塊兒。依賴關係以下圖所示:

宿主依賴業務組件

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"]
    //其餘公共庫,例如圖片選擇、分享、推送等
}
複製代碼

2.3.二、開啓dataBinding

Android MVVM模式離不開DataBinding,每一個組件中都須要開啓,包括宿主App

android {
    //開啓DataBinding
    dataBinding {
        enabled true
    }
}
複製代碼

2.3.三、模式開關

須要一個全局變量來控制當前運行的工程是隔離狀態仍是合併狀態。在gradle.properties中定義:

isBuildModule=false
複製代碼

isBuildModule 爲 true 時可使每一個組件獨立運行,false 則能夠將全部組件集成到宿主 App 中。

2.3.四、debug切換

在組件的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 組件的包名
        }
    }
}
複製代碼

2.3.五、manifest配置

組件在本身的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/*'
                }
            }
        }
    }
}
複製代碼

2.3.六、統一資源

在組件的build.gradle配置統一資源前綴

android {
    //統一資源前綴,規範資源引用
    resourcePrefix "組件名_"
}
複製代碼

2.3.七、配置抽取

能夠將每一個組件的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 {
    ...
}
複製代碼

2.四、完成

運行效果以下:

到此爲止,一個最基本的組件化工程搭建完畢。

三、可行性方案

3.一、組件初始化

組件在獨立運行時,也就是debug期,有單獨的manifest,固然也就能夠指定Application類進行初始化。那麼當組件進行合併的時,Application只能有一個,而且存在宿主App中,組件該如何進行初始化?

3.1.一、反射

反射是一種解決組件初始化的方法。

在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類不能被混淆

3.1.二、初始化接口

定義一個 IModuleInit 接口,動態配置Application,須要初始化的組件實現該接口,統一在宿主app的Application中初始化

public interface IModuleInit {
    //初始化優先的
    boolean onInitAhead(Application application);

    //初始化靠後的
    boolean onInitLow(Application application);
}
複製代碼

3.1.三、初始化實現

反射類和接口都有了,那麼在各自的組件中建立一個初始化類,實現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

3.二、組件間通訊

組件間是徹底無耦合的存在,可是在實際開發中確定會存在業務交叉的狀況,該如何實現無聯繫的組件間通訊呢?

3.2.一、ARouter

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…

3.2.二、事件總線(RxBus)

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);
複製代碼

3.三、base規範

library-base 有兩個主要做用:一是依賴通用基礎jar或第三方框架,二是存放一些公共的靜態屬性和方法。下面列舉一些基礎通用類的約定規範。

3.3.一、config

在base的config包下面,統一存放全局的配置文件,好比組件生命週期初始化類:ModuleLifecycleConfig、ModuleLifecycleReflexs,網絡ROOT_URL,SD卡文件讀寫目錄等。

3.3.二、contract

RxBus組件通訊,須要通過base層,統一規範。那麼能夠在contract包下面定義RxBus的契約類,寫好註釋,便於其餘組件開發人員使用。

3.3.三、global

主要存放全局的Key,好比 IntentKeyGlobal: 存放組件間頁面跳轉傳參的Key名稱; SPKeyGlobal: 全局SharedPreferences Key 統一存放在這裏。單個組件中內部的key能夠另外在單獨組件中定義。

3.3.四、router

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,你的支持則是我前進的動力!

源碼: github.com/goldze/MVVM…

相關文章
相關標籤/搜索