MVC、MVP、MVVM
MoMo Lv5
  1. MVC、MVP、MVVM的他们有什么区别和联系,如何演变的? ⭐⭐⭐⭐⭐
  2. MVVM的优点和缺点 ⭐⭐⭐⭐
  3. 为什么Activity旋转屏幕后ViewModel可以恢复数据 ⭐⭐
  4. ViewModel 的实例缓存到哪儿了 ⭐⭐
  5. 什么时候 ViewModel#onCleared() 会被调用 ⭐⭐

MVC

如果需要设计一套人脸识别系统,用户输入自己姓名后,站在机器前面,机器就可以自动拍照并识别出用户的性别。程序员小帅不考虑软件架构,直接动手撸代码,最终功能虽然实现了,但是各种业务代码都写在同个文件里。过了一段时间,领导让小帅新增识别年龄的功能时,才发现代码非常臃肿,不利于二次开发。看过本文后,小帅重新设计了代码的架构:

  • View:界面相关,将计人脸识别系统UI界面相关代码抽离出来,对应于xml布局文件
  • Controller:控制相关,将和UI界面操作相关以及判断相关的业务逻辑也抽离出来,比如判断用户是否输入姓名了,没有输入则不会开始采集用户照片等,对应于Activity
  • Model:数据相关,具体的性别识别算法运算等较为复杂的运算,本地数据的维护等;

其中View和Model,一个单独负责界面,一个负责处理数据,比较好理解。而Controller就是接收View的指令,做一些逻辑判断或者调用Model做复杂运算后,再把结果反馈给View。通过如此分工,写页面,写业务逻辑,数据运算三者相互分离。

image

然而,在实际的Android开发中,View层对应xml布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller,使得Activity变得臃肿。

而且如果不涉及复杂的运算,那么Controller处理完简单的逻辑判断后就直接返回给View。甚至View可以跳过Controller,直接和Model进行交互。这就造成,本来Controller本来就应该依赖View和Model,而现在View也依赖Controller和Model,耦合性还是太高了,于是我们希望View和Model之间要彻底断开联系。

MVP

在MVC的基础上,用Presenter代替Controller,此时MVP具体为:

  • View:不仅负责UI界面相关操作,还要负责用户交互,相当于Activity
  • Model:依然主要负责数据处理;
  • Presenter:负责View和Model之间的交互;

首先,明确下理解,在MVC中的View相当于布局文件,而在MVP中View相当于Activity;此时View和Model不再直接交互,而是使用Presenter作为二者交互的桥梁。Presenter里同时持有View和Model的引用,View也有Presenter的引用,但没有Model的引用。

当View层中某个UI界面发起某个数据操作时,会调用Presenter的引用,Presenter再调用Model层的引用请求数据。当Model层的数据处理完后会调用Presenter层的回调通知Presenter层数据加载情况,最后Presenter层再调用View层的接口将加载后的数据展示给用户看。Model层是不会直接和View层产生通讯的。

image

MVVM

如果你是做安卓系统开发,那么你可能会接触到MVC或者MVP,而不需要用到MVVM。但如果你是安卓应用开发,那么一定要懂得MVVM。

在MVP中,Presenter更新View也需要很多烦人的代码,有没有什么方法,告诉View去监听某个数据,该数据发生变化时,View自动跟着变化呢?当然是有的,ViewModel随之诞生。ViewModel代替了Presenter,MVP变成了MVVM:Model-View-ViewModel。

  • Model:和MVP中的Model一样,还是用来处理数据的;
  • View:还是用来更新和处理UI界面以及和用户交互,相当于Activity。和MVP里的View的区别在于,View界面的更新从由Presenter驱动,变成了自动监听数据,随着数据变化而自动更新;
  • ViewModel:Model和View的中介,处理逻辑中转任务的媒介。一个View可以绑定多个不同的ViewModel,一个ViewModel也可以被多个View同时绑定。

那么,MVVM是如何做到View的自动更新呢?在此之前,需要掌握几个新概念:

  • LifeCycle:LifeCycle是Google官方提供的管理生命周期事件的方法,可以更加方便地监听声明周期变化事件;
  • LiveData:这是一种用来持有数据的对象,可以设置多个观察者监听LiveData对象,当数据发生改变时,会通知处于active状态(Started和Resume状态)的观察者,并执行设置好的动作。举个例子,我们可以提前设置ActivityA,ActivityB,FragmentA,这三者分别都去监听一个变量名为studentName的LiveDataA对象,也就是在这几个类里,都有下面几行代码。当studentName数值发生改变时,就设置tv_name控件的text改为studentName的新值。那么,当studentName发生改变时,假设ActivityA和FragmentA刚好处于active状态,则ActivityA和FragmentA就会执行tv_name.text = it,自动的修改控件的文本内容。而ActivityB不处于active状态,则不会有任何操作。
1
2
3
myViewModel.studentName.observe(this) {
tv_name.text = it
}
  • DataBinding:这是Googel发布的一个用来支持MVVM模式的框架。主要用来降低布局和逻辑的耦合度。以前都是在xml布局文件写好控件后,在Activity通过findViewById()方法绑定控件后,对控件做操作。而随着DataBinding的产生,我们可以直接在xml布局文件里直接将控件和数据绑定在一起,当数据改变时,控件自动更新。

结合LiveData+DataBinding举个例子增进理解,以下例子参考于MVVMDemo ,:

首先看看布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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>
<!-- 1 -->
<variable
name="image"
type="com.zw.mvvmdemo.bean.ImageUrlBean.UrlBean"/>
</data>


<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/myImageView"
<!-- 2 -->
imageUrl="@{image.BASE_IMAGE_ADDRESS_URL + image.url}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="200dp" />
</android.support.constraint.ConstraintLayout>

</layout>

首先看[注释1],可以创建变量:

1
2
3
<variable
name="image"
type="com.zw.mvvmdemo.bean.ImageUrlBean.UrlBean"/>

在variable标签中:

  • name:需要绑定数据的名称,名称可自定义;
  • type:需要绑定数据的类型;

所以,我们创建了一个名为“image”的变量,并在[注释2]中使用,赋值给了另一个叫imageUrl的属性。imageUrl属性是我们自定义的一个属性,是通过DataBinding的注解BindingAdapter来声明的。

1
2
3
4
5
6
7
8
9
public class GetBingImageAdapter {

@BindingAdapter("imageUrl")
public static void setImage(ImageView iv, String url) { //3
Glide.with(iv)
.load(url)
.into(iv);
}
}

所以当[注释1]名为“image”的变量发生改变时,[注释2]名为“myImageView”的控件中的imageUrl属性就会随着改变,并调用[注释3]的回调函数setImage()方法,最终执行Glide这个第三方图片加载框架来刷新图片。

那么就剩下最后一个问题,[注释1]名为“image”的变量什么时候会发生改变呢?我们可以在Activity中监听从网络获取到的图片的url地址,当url地址改变时,修改“image”变量的值:

1
2
3
4
5
6
7
8
9
10
11
12
mViewModel.getImageUrl().observe(this, new Observer<UrlData<ImageUrlBean.UrlBean>>() {
@Override
public void onChanged(@Nullable UrlData<ImageUrlBean.UrlBean> data) {
mDialog.dismiss();
if(data.getErrorMsg() != null) {
Toast.makeText(BingImageActivity.this, data.getErrorMsg(),
Toast.LENGTH_LONG).show();
}

binding.setImage(data.getData()); //4
}
});

其中getImageUrl()返回的变量的定义是:

1
private MutableLiveData<UrlData<ImageUrlBean.UrlBean>> mData;

没错,就是一个LiveData对象。最后通过[注释4]来修改“image”变量的值。有一个知识点要注意,如果“image”的名字改为“myImageValue”,则[注释4]的函数名应该同步改为setMyImageValue()。

所谓的View能自动更新,其实是因为我们提前设置好了监听,也设置好了监听的对象数值改变后,View需要完成什么动作。

MVVM总结

MVVM的优点和缺点

总的来说,MVVM还是一个非常优秀的模式,主要体现在:

  • 数据和布局双向绑定,我们只需要关心数据数值的改变,而不再需要操心findViewById和setText()等繁复的代码的工作;
  • 一个View可以绑定多个ViewModel,一个ViewModel可以供多个View绑定,提高复用性和灵活性;
  • 自带生命周期管理,不用担心因为生命周期而出现bug;

当然,MVVM也有缺点:

  • View和ViewModel独立,如果出现bug定位问题较难;
  • Binding类的生成可能需要多次Rebuild的操作,若Binding类过多,则编译工作会耗时很大;
  • 需要提前设置好数据监听,如果数值多,那就要设置很多监听;
  • 如果ViewModel层数据未改变,但却强制要View层改变UI控件时,就比较麻烦;

ViewModel 的实例缓存到哪儿了

如果我们修改了系统语言再回到App,或者旋转了屏幕,都会发现APP中当前Activity重新创建了,但是ViewModel里面的LiveData对象实例并没有改变。这就是ViewModel的特性了。我们知道Activity保留现场的方法是onSaveInstanceState(),然后重新创建Activity时,onCreate()里通过Bundle恢复数据。不过该方法仅适合保存可以支持序列化的少量数据。而不适合大量数据。ViewModel的出现就完美解决了这个问题。

我们看看ViewModel的创建方法:

1
2
//MyActivity中
myViewModel = ViewModelProvider(this).get(MyViewModel::class.java) //5

我们创建 ViewModelProvider 对象并传入了 this 参数,即所在的MyActivity,然后通过 ViewModelProvider#get方法,传入 MyViewModel 的 class 类型,然后拿到了 MyViewModel 实例 myViewModel。看看ViewModelProvider的构造方法:

1
2
3
4
5
6
// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { //6
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory //7
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}

ViewModelProvider的构造函数,即[注释6]的ViewModelStoreOwner是什么?明明我们在 [注释5]传入的是this,即MyActivity。其实,MyActivity继承于ComponentActivity,ComponentActivity 类又实现了ViewModelStoreOwner 接口。所以[注释7]调用的owner.getViewModelStore()就是ComponentActivity类中的getViewModelStore()所返回的ViewModelStore 实例。同时,[注释7]的这个this()方法,就对应了ViewModelProvider的另一个构造函数:

1
2
3
4
5
// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store; //8
}

所以[注释7]ComponentActivity类中的getViewModelStore()所返回的ViewModelStore 实例,最终是存放到了[注释8],ViewModelProvider类中的mViewModelStore变量里。

get()方法:

1
2
3
4
5
6
7
8
9
// ViewModelProvider.java
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass); //9
}

获取[注释5]传入的”MyViewModel::class.java” 的 CanonicalName , 调用了另一个 get 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ViewModelProvider.java
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 根据传入的key名称,从mViewModelStore缓存中查找是否有对应的已缓存的viewModel
ViewModel viewModel = mViewModelStore.get(key);

//如果有
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
//那么直接返回传入的key对应的viewModel
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//如果没有,那么使用工厂模式创建 ViewModel 实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
//新创建的viewModel和对应的key存入mViewModelStore做缓存,方便下次重新从缓存中获取
mViewModelStore.put(key, viewModel);
// 返回新创建的viewModel
return (T) viewModel;
}

从put()和get(),mViewModelStore应该是一个map对象,可以通过key值找到对应的value:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    // ViewModelProvider.java中mViewModelStore 的定义
private final ViewModelStore mViewModelStore;

// ViewModelStore的定义在ViewModelStore.java类中
public class ViewModelStore {

private final HashMap<String, ViewModel> mMap = new HashMap<>(); //10

final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}

final ViewModel get(String key) {
return mMap.get(key);
}

Set<String> keys() {
return new HashSet<>(mMap.keySet());
}

/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() { //14
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}

在[注释10]可以确定了,mViewModelStore变量确实是一个Map对象,保存着不同Activity对应的viewModel。比如[注释5]传入的MyViewModel::class.java,mViewModelStore这个Map中就缓存着MyViewModel::class.java对应的viewModel。

“ViewModel 的实例缓存到哪儿了”?
ViewModel 的实例缓存到ViewModelProvider这个类中的mViewModelStore这个变量里。

为什么Activity旋转屏幕后ViewModel可以恢复数据

我们回到[注释8]mViewModelStore = store中的store是[注释7]owner.getViewModelStore()提供的。也就是ComponentActivity.java类中的getViewModelStore()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ComponentActivity.java
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
// mViewModelStore为null时
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance(); // 11
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
// mViewModelStore还为null时,才手动创建
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

关键看看[注释11],当mViewModelStore为null时,会调用 getLastNonConfigurationInstance() 获取 NonConfigurationInstances 对象 nc,当nc不为null时,赋值nc.viewModelStore给mViewModelStore。也就是说,之前创建的 viewModelStore 对象被缓存在 NonConfigurationInstances 中。也就是说,我们可以通过[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象。

那为什么可以通过[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象呢?这是因为Activity 在因配置更改而销毁重建过程中会先调用 onRetainNonConfigurationInstance()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 保留所有适当的非配置状态
*/
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();

ViewModelStore viewModelStore = mViewModelStore; //12
// 若 viewModelStore 为空,则尝试从 getLastNonConfigurationInstance() 中获取
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
// 依然为空,说明没有需要缓存的,则返回 null
if (viewModelStore == null && custom == null) {
return null;
}

// 创建 NonConfigurationInstances 对象,并赋值 viewModelStore
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore; //13
return nci;
}

在[注释12-13],会将现有的mViewModelStore进行缓存.当Activity重建后,就可以调用[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象了。onRetainNonConfigurationInstance 方法和 getLastNonConfigurationInstance 是成对出现的。

“为什么Activity旋转屏幕后ViewModel可以恢复数据”?
因为Activity 因屏幕旋转而销毁时,mViewModelStore对象在onRetainNonConfigurationInstance()方法中缓存了下来,直到Activity重建后,通过getLastNonConfigurationInstance()重新获取出来。

什么时候 ViewModel#onCleared() 会被调用

我们看看4.2小节,ViewModelStore的代码中的[注释14]就有

1
2
3
4
5
6
7
8
9
10
11
12
    // ViewModelStore的定义在ViewModelStore.java类中
public class ViewModelStore {

...

public final void clear() { //14
for (ViewModel vm : mMap.values()) {
vm.clear(); //15
}
mMap.clear();
}
}

最终在[注释15]vm.clear(),就调用了ViewModel的clear()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class ViewModel {
protected void onCleared() {
}

@MainThread
final void clear() {
mCleared = true;

if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared(); //16
}
}

而ViewModel的clear()方法最终会执行[注释16]onCleared()。因此什么时候执行 ViewModel#onCleared() ,就看什么时候执行 ViewModelStore 的 clear() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ComponentActivity.java
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) { //17
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) { //18
getViewModelStore().clear(); //19
}
}
}
});
...
}

在[注释17-18],只要当前Activity处于ON_DESTROY状态且isChangingConfigurations() 返回 false,即当前Activity销毁不是因为 Configuration 的改变而被销毁的。就执行[注释19]getViewModelStore().clear()。进而执行 ViewModel#onCleared() 。

Powered by Hexo & Theme Keep
Unique Visitor Page View