(一)Android官方MVVM框架實(shí)現(xiàn)組件化之整體結(jié)構(gòu)

作者: Dawish_大D
簡書: http://www.lxweimin.com/u/40e1f22c2c53

(一)Android官方MVVM框架實(shí)現(xiàn)組件化之整體結(jié)構(gòu)
(二)Android官方MVVM框架實(shí)現(xiàn)組件化之ARouter串聯(lián)各模塊

目前的項(xiàng)目結(jié)構(gòu)圖置頂:Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo

0-演示項(xiàng)目MVVM組件化架構(gòu)圖

一、google官方MVVM框架講解

我前面對(duì)比了MVC和MVP《兩張圖看懂Android開發(fā)中MVC與MVP的區(qū)別》,可以相對(duì)于MVC我們的MVP是有多優(yōu)越,但是Android開發(fā)現(xiàn)在已經(jīng)開始流行了MVVM,前不久google官方發(fā)布了MVVM的正式庫。官方的正式MVVM庫主要包括下面四個(gè):

1-正式MVVM庫組件

其中只有ViewModel是MVVM結(jié)構(gòu)中的一個(gè)組件,其他的三個(gè)都是輔助性質(zhì)的。

lifecycles 就是處理UI界面的生命周期,在26版本以后的Support庫中,AppCompatActivitySupportActivity中都實(shí)現(xiàn)了LifecycleOwner,內(nèi)部已經(jīng)對(duì)UI界面的生命周期做了處理了。

LiveData是一個(gè)抽象類,我們可以存放UI頁面需要的數(shù)據(jù),就是把數(shù)據(jù)包裝在LiveData中了,我們可以觀測LiveData中的數(shù)據(jù)變化,但是LiveData是跟UI的生命周期關(guān)聯(lián)的,當(dāng)UI頁面銷毀了,LiveData的數(shù)據(jù)變化回調(diào)是不會(huì)執(zhí)行的。

Room 就是一個(gè)sqlite數(shù)據(jù)持久化庫,我們也可以使用別的ORM庫。


二、MVVM架構(gòu)優(yōu)勢

《兩張圖看懂Android開發(fā)中MVC與MVP的區(qū)別》 前面兩張圖真是了MVC和MVP的區(qū)別,我這里也來一張圖看看MVVM:

2-MVVM架構(gòu)

看上圖ModelView是不會(huì)發(fā)生關(guān)系的,ViewModel是把View和Model關(guān)聯(lián)起來的加工廠:

3-ViewModel工廠

MVVM優(yōu)勢總結(jié):

  1. ViewModel雙向綁定,一方的改變都會(huì)影響另一方,開發(fā)者不用再去手動(dòng)修改UI的數(shù)據(jù)。額,互相自動(dòng)的。

  2. 不需要findViewById也不需要butterknife,不需要拿到具體的View去設(shè)置數(shù)據(jù)綁定監(jiān)聽器等等,這些都可以用DataBinding完成。是不是很舒服?

  3. ViewModel的雙向綁定是支持生命周期檢測的,不會(huì)擔(dān)心頁面銷毀了還有回調(diào)發(fā)生,這個(gè)由lifeCycle完成。

  4. 不會(huì)像MVC一樣導(dǎo)致Activity中代碼量巨大,也不會(huì)像MVP一樣出現(xiàn)大量的ViewPresenter接口。項(xiàng)目結(jié)構(gòu)更加低耦合。

  5. 更低的耦合把各個(gè)模塊分開開發(fā),分開測試,可以分給不同的開發(fā)人員來完成。


三、MVVM組件化示例項(xiàng)目架構(gòu)分析

下圖是項(xiàng)目模塊和工程之間的依賴關(guān)系:


4-MVVM組件化示例項(xiàng)目架構(gòu)圖

下圖是工程Android Studio中的目錄結(jié)構(gòu):


5-工程目錄結(jié)構(gòu)

3.1 各模塊和彼此之間的關(guān)系解釋:

  • lib_opensource :第三方build.gradle依賴,本項(xiàng)目主要有supportlifecycleroomfrescoretrofitokhttpRxJavaARouter這些。

  • lib_coremodel: 存放MVVM中的ModelViewModel兩個(gè)模塊,就是數(shù)據(jù)的處理和數(shù)據(jù)與UI頁面的綁定。依賴lib_opensource庫。

  • lib_common : 公共庫,主要有各種base,各種ui組件,自定義組件,公用的Activity、公用的Fragment,和公用的utils等等。依賴lib_coremodel庫。

  • module_girls : 妹子功能模塊,可以在libraryapplication之間切換,自己可以是一個(gè)app也可以成為別的app的一個(gè)組件模塊。組件化編譯時(shí)為app,反之為module。

  • module_news : 新聞功能模塊,可以在libraryapplication之間切換,自己可以是一個(gè)app也可以成為別的app的一個(gè)組件模塊。組件化編譯時(shí)為app,反之為module。

  • app_universal : 定制版本的app,組件化編譯時(shí) module_girlsmodule_news為app,所以不能把這兩個(gè)作為module加進(jìn)來編譯,所以組件化編譯時(shí)app_universal要依賴lib_common庫,反之就可以把 module_girlsmodule_news作為module加進(jìn)來編譯。

  • app_specific : 定制版本的app,組件化編譯時(shí) module_girlsmodule_news為app,所以不能把這兩個(gè)作為module加進(jìn)來編譯,所以組件化編譯時(shí)app_specific要依賴lib_common庫,反之就可以把 module_girlsmodule_news作為module加進(jìn)來編譯。

3.2 ARouter串聯(lián)各個(gè)模塊

使用ARouter來跳轉(zhuǎn)Activity和獲取Fragment,記得看之前別人的組件化結(jié)構(gòu)文章,一直都在糾結(jié)Fragment的獲取問題,我想說的是有了ARouter來獲取Fragment不是超級(jí)簡單么?

ARouter典型應(yīng)用

  • 從外部URL映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
  • 跨模塊頁面跳轉(zhuǎn),模塊間解耦
  • 攔截跳轉(zhuǎn)過程,處理登陸、埋點(diǎn)等邏輯
  • 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦

3.3 組件化編譯和非組件化編譯切換

我們?cè)诠こ谈夸浵碌?code>gradle.properties文件中加入一個(gè)Boolean類型的變量,通過修改這個(gè)變量來識(shí)別編譯模式:

# 每次更改“isModule”的值后,需要點(diǎn)擊 "Sync Project" 按鈕
# isModule是“集成開發(fā)模式”和“組件開發(fā)模式”的切換開關(guān)
isModule=false

然后在 module_girlsmodule_news中的build.gradle文件中支持切換:

if (isModule.toBoolean()) {
    //組件化編譯時(shí)為application
    apply plugin: 'com.android.application'
} else {
    //非組件化編譯時(shí)為library
    apply plugin: 'com.android.library'
}

android {
    compileSdkVersion build_versions.target_sdk
    buildToolsVersion build_versions.build_tools

    defaultConfig {
        minSdkVersion build_versions.min_sdk
        targetSdkVersion build_versions.target_sdk
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //ARouter
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled = true
    }
    lintOptions {
        abortOnError false
    }
    sourceSets {
        main {
            if (isModule.toBoolean()) {
                //組件化編譯時(shí)為app,在對(duì)應(yīng)的AndroidManifest文件中需要寫ndroid.intent.action.MAIN入口Activity
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成開發(fā)模式下排除debug文件夾中的所有Java文件
                java {
                    //debug文件夾中放的是Application類,非組件化時(shí)不用有此類
                    exclude 'debug/**'
                }
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    api project(':lib_coremodel')
    api project(':lib_common')
    implementation 'com.android.support:support-v4:26.1.0'
    annotationProcessor deps.arouter.compiler
}

上面看到了組件化和非組件化編譯會(huì)有不用的AndroidManifest文件,組件化時(shí)需要debug文件夾下面的application類,非組件化時(shí)排除此文件夾。

6-組件化非組件化編譯切換

  • module下的AndroidManifest文件是組件化app編譯時(shí)的,寫了MAIN入口Activity
  • dubug下是組件化app編譯時(shí)的Application類,初始化作為一個(gè)app運(yùn)行時(shí)需要的資源等等。在非組件化編譯在build.gradle文件中排除debug文件夾的所以東西。

3.4 最后預(yù)告:

后面會(huì)有一些列介紹在MVVM組件化過程中使用ARouter來跳轉(zhuǎn)Activity和獲取FragmentDataBinding實(shí)現(xiàn)數(shù)據(jù)和UI的互相綁定、Rxjava2Retrofit2動(dòng)態(tài)數(shù)據(jù)獲取,和AndroidViewModel的封裝。

下面貼貼一個(gè)lib_coremodel庫中我封裝的AndroidViewModel,用泛型來確定數(shù)據(jù)類型,并且是動(dòng)態(tài)URL獲取數(shù)據(jù):

package google.architecture.coremodel.viewmodel;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.databinding.ObservableField;
import android.support.annotation.NonNull;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;

import google.architecture.coremodel.datamodel.http.ApiClient;
import google.architecture.coremodel.datamodel.http.ApiConstants;
import google.architecture.coremodel.datamodel.http.service.DynamicApiService;
import google.architecture.coremodel.util.JsonUtil;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;

/**
 * Created by dxx on 2017/11/20.
 */

public class BaseViewModel<T> extends AndroidViewModel {

    //生命周期觀察的數(shù)據(jù)
    private MutableLiveData<T>  liveObservableData = new MutableLiveData<>();
    //UI使用可觀察的數(shù)據(jù) ObservableField是一個(gè)包裝類
    public ObservableField<T> uiObservableData = new ObservableField<>();

    private final CompositeDisposable mDisposable = new CompositeDisposable();

    private static final MutableLiveData ABSENT = new MutableLiveData();
    {
        //noinspection unchecked
        ABSENT.setValue(null);
    }


    public BaseViewModel(@NonNull Application application, String fullUrl) {
        super(application);
        ApiClient.initService(ApiConstants.GankHost, DynamicApiService.class).getDynamicData(fullUrl).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
            @Override
            public void onSubscribe(Disposable d) {
                mDisposable.add(d);
            }

            @Override
            public void onNext(ResponseBody value) {
               if(null != value){
                   try {
                       liveObservableData.setValue(JsonUtil.Str2JsonBean(value.string(), getTClass()));
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
        });
    }

    /**
     * LiveData支持了lifecycle生命周期檢測
     * @return
     */
    public LiveData<T> getLiveObservableData() {
        return liveObservableData;
    }

    /**
     * 當(dāng)主動(dòng)改變數(shù)據(jù)時(shí)重新設(shè)置被觀察的數(shù)據(jù)
     * @param product
     */
    public void setUiObservableData(T product) {
        this.uiObservableData.set(product);
    }

    public Class<T> getTClass(){
        Class<T> tClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return tClass;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        mDisposable.clear();
    }
}

Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容