Android端MVVM從入門到實(shí)戰(zhàn)(第一篇) - MVVM和四大官方組件

1、MVVM簡(jiǎn)介

MVVM是相對(duì)于MVC和MVP的一個(gè)概念,是一種架構(gòu)模式。

1.1 MVC

傳統(tǒng)的MVC中,View改變通知Controller進(jìn)行處理,Controller處理結(jié)束后通知Model層更新,Model層更新以后通知View層渲染,指令單項(xiàng)流動(dòng),角色分工明確。但是MVC有三個(gè)缺點(diǎn),1、三個(gè)角色互相持有對(duì)方依賴,因此很難復(fù)用其中任意一方;2、開發(fā)時(shí)必須三個(gè)模塊同步開發(fā),否則很難相互匹配;3、由于每一個(gè)角色的改變都會(huì)直接或間接的影響另外兩個(gè)角色,所以任何改動(dòng)都必須考慮全盤影響。

Untitled.png

1.2 MVP

MVP解決了以上三個(gè)問題,MVP中的Presenter層相當(dāng)于MVC中的Controller層,但有一個(gè)變動(dòng):Presenter分別和Model層以及View層雙向交互,而Model層與View層之間不再直接交互,并且Presenter來定義Model層和View層各自要實(shí)現(xiàn)的功能。這個(gè)變動(dòng)解決了之前所提的三個(gè)問題,首先Model層和View層可以任意替換,只要代替者能實(shí)現(xiàn)Presenter層定義的接口即可,這樣保證了Model層和View層的可復(fù)用性;其次在Presenter層定義了Model層和View層需要實(shí)現(xiàn)的功能后,Model層和View層可以分別開發(fā),有Presenter層去處理兩者適配的問題;最后Model層和View層脫離以后任意一方的改動(dòng)只要Presenter層進(jìn)行適配即可,不會(huì)再影響到對(duì)方,降低了修改代碼時(shí)的影響范圍。

Untitled 1.png

1.3 MVVM

MVVM是MVP模式進(jìn)一步發(fā)展的產(chǎn)物,通過語言或框架的支持,開發(fā)人員不需要再手動(dòng)處理Model改變以后View的更新,而是通過訂閱-觀察的模式讓View在Model改變時(shí)自動(dòng)更新。這個(gè)模式雖然有一定的學(xué)習(xí)成本,但優(yōu)點(diǎn)(尤其是在Android端)也清晰可見:1、繼承了MVP的所有優(yōu)點(diǎn),擁有較好的可復(fù)用性和可維護(hù)性,并且view層和model層可以分別開發(fā);2、方便測(cè)試,由于Model和View在框架層面上進(jìn)行綁定,理論上只要數(shù)據(jù)正常且綁定的方式合理,View顯示就不會(huì)有問題,這樣可以針對(duì)Model層和ViewModel層進(jìn)行單元測(cè)試,而不用再考慮View層;3、Android中的生命周期、activity重建等問題不需要開發(fā)人員考慮,由jetpack提供的組件進(jìn)行處理。

Untitled.jpeg

Android端的MVVM架構(gòu)的實(shí)現(xiàn)基于jetpack組件包中的四個(gè)組件:Databinding、LiveData、ViewModel、Lifecycle,接下來我們?cè)谝粋€(gè)簡(jiǎn)單的案例中分別了解一下這四個(gè)組件。

2、分別講解

參考代碼地址:https://github.com/guoergongzi/GMVVMDemo/tree/main

2.1 LifeCycle

在Android代碼解耦的過程中,處理生命周期是一個(gè)很核心的問題,一個(gè)普通組件必須要依賴系統(tǒng)組件(如Activity、Fragment)的調(diào)用才能知道自己方法運(yùn)行的時(shí)機(jī),LifeCycle這個(gè)組件正是為了解決這個(gè)問題而生。

LifeCycle提供了兩個(gè)接口:LifecycleOwner和LifecycleObserver;只要讓實(shí)現(xiàn)了LifecycleObserver接口的普通組件去訂閱實(shí)現(xiàn)了LifecycleOwner的系統(tǒng)組件,就可以讓該組件可以感知到系統(tǒng)組件的生命周期,并在對(duì)應(yīng)的時(shí)機(jī)去處理自身的邏輯。

下圖中可以看到,我們現(xiàn)在版本源碼中的Activity已經(jīng)實(shí)現(xiàn)了LifecycleOwner,因此我們只要編寫LifecycleObserver的實(shí)現(xiàn)并讓它訂閱Activity即可。同理Fragment也實(shí)現(xiàn)了這個(gè)接口,這里只以Activity做一個(gè)演示。

Untitled 2.png

我們編寫如下LifecycleObserver實(shí)現(xiàn)類:

public class TestClass implements DefaultLifecycleObserver {

    @Override
    public void onResume(@NonNull LifecycleOwner owner) {
        DefaultLifecycleObserver.super.onResume(owner);
        Toast.makeText((Context) owner, "測(cè)試內(nèi)容", Toast.LENGTH_SHORT).show();
    }
}

在activity的onCreate()方法中用以下代碼訂閱:

getLifecycle().addObserver(new TestClass());

這樣當(dāng)Activity顯示時(shí)我們的測(cè)試Toast就會(huì)顯示出來,相當(dāng)于直接寫在Activity的onResume()回調(diào)中。

這里只演示了一個(gè)簡(jiǎn)單的Activity中使用Lifecycle的方式,之后的內(nèi)容中我會(huì)詳細(xì)的分享關(guān)于Lifecycle的更多知識(shí),這里我們先接著了解我們的下一個(gè)組件。

參考代碼Module:glifecycledemo

2.2 ViewModel

ViewModel是Google提供的Android端實(shí)現(xiàn)MVVM中的VM層的標(biāo)準(zhǔn)方式,它在創(chuàng)建時(shí)需要一個(gè)LifecycleOwner作為參數(shù),在這個(gè)LifecycleOwner銷毀時(shí)它自己也會(huì)銷毀(Activity旋轉(zhuǎn)造成的Activity銷毀和重建并不會(huì)觸發(fā))。

把邏輯放在ViewModel中既可以給Activity(或Fragment等其它LifecycleOwner)瘦身,又能避免Activity旋轉(zhuǎn)造成數(shù)據(jù)丟失。

下面我們用一個(gè)小案例來了解一下ViewModel的基本使用方式,首先先創(chuàng)建一個(gè)ViewModel子類,寫一些邏輯在里面。

public class TestViewModel extends ViewModel {

    private Timer timer;
    private int timeCount = 0;

    public void startTimer() {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                timeCount++;
                Log.v("G", "timeCount = " + timeCount);
            }
        };
        timer = new Timer();
        timer.schedule(timerTask, 1000, 1000);
    }

    public int getTimeCount() {
        return timeCount;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        timer.cancel();
    }
}

然后在Activity的onCreate中寫下以下代碼來綁定Activity和ViewModel。

TestViewModel viewModel = new ViewModelProvider(NewActivity.this).get(TestViewModel.class);
viewModel.startTimer();

我們可以再寫一個(gè)按鈕來測(cè)試getTimeCount()方法。

Button timeButton = findViewById(R.id.btn_view_model_demo);

timeButton.setOnClickListener(view -> {
    Toast.makeText(NewActivity.this, "計(jì)時(shí)到第" + viewModel.getTimeCount() + "秒", Toast.LENGTH_SHORT).show();
});

這樣我們點(diǎn)擊按鈕時(shí)就可以看到Activity正確的獲取到ViewModel中定義的值了。

在項(xiàng)目運(yùn)行過程中,如果我們旋轉(zhuǎn)一下手機(jī)畫面,我們會(huì)發(fā)現(xiàn)timeCount依然在之前的數(shù)值上累積,而沒有因?yàn)樾D(zhuǎn)歸零,這點(diǎn)驗(yàn)證了ViewModel可以避免Activity旋轉(zhuǎn)造成的數(shù)據(jù)丟失。我們退出Activity,會(huì)發(fā)現(xiàn)我們寫在計(jì)時(shí)器里面的日志停下來了,說明Activity的銷毀可以銷毀ViewModel并觸發(fā)它的onCleared()方法。

這里用一個(gè)簡(jiǎn)單的例子介紹了ViewModel,和Lifecycle一樣,ViewModel還有很多用法和知識(shí)需要我們了解,但是我們把這部分也放在之后的文章中。另外我們還沒有了解LiveData和DataBinding,接下來我們了解了這兩個(gè)框架以后,我們會(huì)發(fā)現(xiàn)這個(gè)Demo里的ViewModel有更好的寫法。

參考代碼Module:gviewmodeldemo

2.3 LiveData

LiveData是一種可觀察的數(shù)據(jù)存儲(chǔ)器類,相比于其它可觀察類,它擁有感知生命周期的作用,確保它僅在應(yīng)用組件處在活躍中時(shí)才會(huì)被觸發(fā)。

我們把ViewModel部分的案例稍加改造,用MutableLiveData來包裝timeCount參數(shù),通過setValue()或postValue()方法來更新數(shù)值。

public class TestViewModel extends ViewModel {

    private Timer timer;
    private MutableLiveData<Integer> timeCount = new MutableLiveData<>();

    {
        timeCount.setValue(0);
    }

    public void startTimer() {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                timeCount.postValue(timeCount.getValue() + 1);
            }
        };
        timer = new Timer();
        timer.schedule(timerTask, 1000, 1000);
    }

    public Z<Integer> getTimeCount() {
        return timeCount;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        timer.cancel();
    }
}

然后在Activity的onCreate()中添加觀察者并找一個(gè)TextView來顯示timeCount的值。

TestViewModel viewModel = new ViewModelProvider(MainActivity.this).get(TestViewModel.class);
viewModel.startTimer();

TextView textView = findViewById(R.id.tv_live_data_demo);
LifecycleOwner owner = MainActivity.this;
viewModel.getTimeCount().observe(owner, integer -> {
    textView.setText("計(jì)時(shí)到第" + integer + "秒");
    Log.v("G", "timeCount = " + integer);
});

運(yùn)行起來后我們會(huì)發(fā)現(xiàn),TextView的內(nèi)容會(huì)不停的刷新,這就是LiveData的主要作用——在數(shù)值更新時(shí)通知注冊(cè)的觀察者。

到這里為止,LiveData看起來和EventBus之類的事件通知框架區(qū)別不大,那么為什么我們要在MVVM中使用這個(gè)組件呢?核心原因有幾點(diǎn):

1、LiveData在Activity等LifecycleOwner不活躍時(shí)不會(huì)發(fā)布通知,上圖的案例中我們把日志打印的代碼從計(jì)時(shí)器中移到了觀察者回調(diào)中,我們把Activity退到后臺(tái)時(shí)會(huì)發(fā)現(xiàn)日志不再打印了,這可以避免我們界面不可見時(shí)依然處理事件造成手機(jī)運(yùn)行資源的浪費(fèi)。

2、LiveData和Activity生命周期同步,不易發(fā)生內(nèi)存泄漏,也不用再界面銷毀時(shí)處理它。

3、我們的代碼中聲明timeCount對(duì)象時(shí)用了MutableLiveData類,提供get方法時(shí)卻返回了LiveData類,MutableLiveData類是LiveData類的子類,它們的區(qū)別是MutableLiveData可以調(diào)用postValue()或setValue()方法更新內(nèi)容,LiveData類卻不行。從架構(gòu)的角度看,這種方式保證了數(shù)據(jù)只會(huì)在VM層更新,不會(huì)被View層更新——因?yàn)閂iew層通過get方法得到的是無法更新的LiveData類。

4、LiveData和我們之后要介紹的DataBinding可以實(shí)現(xiàn)配合。

可以看到,我們訂閱LiveData時(shí)需要提供一個(gè)LifecycleOwner對(duì)象,就是這個(gè)參數(shù)讓LiveData有了感知Activity生命周期的能力。我們?cè)谶M(jìn)行MVVM開發(fā)時(shí)有時(shí)候并不會(huì)直接用到LifeCycle,但它是支持Android端MVVM實(shí)現(xiàn)的重要組件,這也是這篇文章要介紹它的原因。

參考代碼Module:glivedatademo

2.4 DataBinding

到目前為止,MVVM的核心特征——Model改變時(shí)數(shù)據(jù)自動(dòng)變化我們?nèi)匀粵]有看到。當(dāng)我們想要在textView中顯示內(nèi)容時(shí),還是要在觀察者模式里手動(dòng)的調(diào)用setText,沒錯(cuò),這就是DataBinding這個(gè)組件為我們解決的問題。

首先我們要在module目錄下的build.gradle文件中添加以下代碼,允許項(xiàng)目使用Databinding

android {
        。。。
    // 允許項(xiàng)目使用databinding
    dataBinding {
        enabled = true
    }
}

然后我們更改一下上一個(gè)Demo中的TestViewModel,把返回的LiveData類型改成拼接好的字符串timeCountString:

public class TestViewModel extends ViewModel {

    private Timer timer;
    private final MutableLiveData<String> timeCountString = new MutableLiveData<>();
    private int timeCount = 0;

    {
        timeCountString.setValue("計(jì)時(shí)到第" + timeCount + "秒");
    }

    public void startTimer() {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                timeCount += 1;
                timeCountString.postValue("計(jì)時(shí)到第" + timeCount + "秒");
            }
        };
        timer = new Timer();
        timer.schedule(timerTask, 1000, 1000);
    }

    public LiveData<String> getTimeCount() {
        return timeCountString;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        timer.cancel();
    }
}

接下來我們要把界面的布局文件特殊處理一下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="<http://schemas.android.com/apk/res/android>"
    xmlns:app="<http://schemas.android.com/apk/res-auto>"
    xmlns:tools="<http://schemas.android.com/tools>">

    <data>

        <variable
            name="vm"
            type="com.gegz.gdatabindingdemo.TestViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@{vm.timeCount}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

可以看到,我們的布局文件和不使用DataBinding時(shí)的樣子有很大不同:

1、我們用layout包裹了原本最外層的布局,并且在原本最外層布局的同級(jí)添加了一個(gè)data標(biāo)簽,在其中用添加了一個(gè)name為vm、type為TestViewModel的variable標(biāo)簽。

2、我們沒有給TextView設(shè)置Id,取而代之的是我們直接給它設(shè)置了text屬性,并且在屬性里使用了@{vm.timeCount}的寫法。

最后我們?cè)贛ainActivity的onCreate中加載ViewModel和DataBinding:

ActivityMainBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mBinding.setLifecycleOwner(this);
TestViewModel viewModel = new ViewModelProvider(this).get(TestViewModel.class);
viewModel.startTimer();
mBinding.setVariable(BR.vm, viewModel);

這里有幾個(gè)需要注意的地方:

1、我們調(diào)用了DataBindingUtil的setContentView就不用再調(diào)用Activity的setContentView了,如果我們點(diǎn)進(jìn)去看看源碼,就會(huì)發(fā)現(xiàn)DataBindingUtil的setContentView調(diào)用了Activity的這個(gè)方法。

2、我們這里用到了一個(gè)ActivityMainBinding,但我們并沒有聲明它,不用擔(dān)心,這個(gè)類是系統(tǒng)自動(dòng)生成的。

3、我們給ActivityMainBinding的對(duì)象設(shè)置了viewModel作為數(shù)據(jù)源,這個(gè)地方也可以用普通的實(shí)體類,但使用ViewModel可以更好的解耦邏輯層的代碼,并且能靈活的處理一些邏輯。

參考代碼Module:gdatabindingdemo

3、總結(jié)

到最后一個(gè)Demo為止,我們已經(jīng)把上面提到過的四個(gè)組件——LifeCycle、ViewModel、LiveData和DataBinding都用上了,并且了解了這四個(gè)組件各自扮演的角色。

結(jié)合我們文章開頭對(duì)MVVM架構(gòu)的介紹來分析我們這個(gè)Demo,會(huì)發(fā)現(xiàn)Demo中的Activity只負(fù)責(zé)將DataBinding、ViewModel和自己綁定起來,TestViewModel類中包含了我們這個(gè)界面的邏輯——計(jì)時(shí)并更新數(shù)據(jù),而xml文件則通過框架的支持完成了界面的顯示和更新,View層和ViewModel層之間的分工體現(xiàn)的十分明確。有一個(gè)遺憾是我們還沒有一個(gè)獨(dú)立的Model層,畢竟我們這個(gè)Demo的功能還太過簡(jiǎn)單,給它添加一個(gè)單獨(dú)的Model層難免有過度設(shè)計(jì)之嫌,我們會(huì)在這個(gè)系列之后的文章中演示Model層的寫法。

在這篇文章里,我們已經(jīng)對(duì)android端的MVVM有一個(gè)基本的理解,并且實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的小界面,但是很明顯,這種程度的理解還遠(yuǎn)遠(yuǎn)不夠我們?nèi)ネ瓿蓪?shí)際項(xiàng)目,我們還需要學(xué)很多知識(shí),比如怎么加載網(wǎng)絡(luò)圖片,比如怎么編寫fragment和recyclerView,比如我們之前提到過的Model層怎么設(shè)計(jì),這些我們都會(huì)在之后的文章中介紹,希望大家多多點(diǎn)贊收藏,期待在下一篇文章中與大家討論更詳細(xì)的Android端MVVM知識(shí)。

注:出于篇幅考慮,本文中很多代碼片段不完整,大家可以從Github上下載Demo學(xué)習(xí)。

代碼地址:

參考文檔:

知乎:MVC、MVP、MVVM -- 可多C

CSDN:三種架構(gòu)模式——MVC、MVP、MVVM -- 非早起選手

CSDN:Android LifeCycle詳解 -- 優(yōu)雅的心情

CSDN:Android ViewModel詳解 -- 趙彥軍

CSDN:Android LiveData 詳解及使用 -- 大腸包小腸|

CSDN:Android DataBinding的基本使用 -- 尹中文

CSDN:Android DataBinding 從入門到進(jìn)階,看這一篇就夠 -- 程序員一東

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容