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)都必須考慮全盤影響。

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í)的影響范圍。

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)行處理。

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è)演示。

我們編寫如下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í)。
代碼地址:
參考文檔:
CSDN:三種架構(gòu)模式——MVC、MVP、MVVM -- 非早起選手
CSDN:Android LifeCycle詳解 -- 優(yōu)雅的心情
CSDN:Android ViewModel詳解 -- 趙彥軍
CSDN:Android LiveData 詳解及使用 -- 大腸包小腸|