
- MVC、MVP、MVVM的他们有什么区别和联系,如何演变的? ⭐⭐⭐⭐⭐
- MVVM的优点和缺点 ⭐⭐⭐⭐
- 为什么Activity旋转屏幕后ViewModel可以恢复数据 ⭐⭐
- ViewModel 的实例缓存到哪儿了 ⭐⭐
- 什么时候 ViewModel#onCleared() 会被调用 ⭐⭐
MVC
如果需要设计一套人脸识别系统,用户输入自己姓名后,站在机器前面,机器就可以自动拍照并识别出用户的性别。程序员小帅不考虑软件架构,直接动手撸代码,最终功能虽然实现了,但是各种业务代码都写在同个文件里。过了一段时间,领导让小帅新增识别年龄的功能时,才发现代码非常臃肿,不利于二次开发。看过本文后,小帅重新设计了代码的架构:
- View:界面相关,将计人脸识别系统UI界面相关代码抽离出来,对应于xml布局文件;
- Controller:控制相关,将和UI界面操作相关以及判断相关的业务逻辑也抽离出来,比如判断用户是否输入姓名了,没有输入则不会开始采集用户照片等,对应于Activity;
- Model:数据相关,具体的性别识别算法运算等较为复杂的运算,本地数据的维护等;
其中View和Model,一个单独负责界面,一个负责处理数据,比较好理解。而Controller就是接收View的指令,做一些逻辑判断或者调用Model做复杂运算后,再把结果反馈给View。通过如此分工,写页面,写业务逻辑,数据运算三者相互分离。
然而,在实际的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层产生通讯的。
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 | myViewModel.studentName.observe(this) { |
- DataBinding:这是Googel发布的一个用来支持MVVM模式的框架。主要用来降低布局和逻辑的耦合度。以前都是在xml布局文件写好控件后,在Activity通过findViewById()方法绑定控件后,对控件做操作。而随着DataBinding的产生,我们可以直接在xml布局文件里直接将控件和数据绑定在一起,当数据改变时,控件自动更新。
结合LiveData+DataBinding举个例子增进理解,以下例子参考于MVVMDemo ,:
首先看看布局文件:
1 |
|
首先看[注释1],可以创建变量:
1 | <variable |
在variable标签中:
- name:需要绑定数据的名称,名称可自定义;
- type:需要绑定数据的类型;
所以,我们创建了一个名为“image”的变量,并在[注释2]中使用,赋值给了另一个叫imageUrl的属性。imageUrl属性是我们自定义的一个属性,是通过DataBinding的注解BindingAdapter来声明的。
1 | public class GetBingImageAdapter { |
所以当[注释1]名为“image”的变量发生改变时,[注释2]名为“myImageView”的控件中的imageUrl属性就会随着改变,并调用[注释3]的回调函数setImage()方法,最终执行Glide这个第三方图片加载框架来刷新图片。
那么就剩下最后一个问题,[注释1]名为“image”的变量什么时候会发生改变呢?我们可以在Activity中监听从网络获取到的图片的url地址,当url地址改变时,修改“image”变量的值:
1 | mViewModel.getImageUrl().observe(this, new Observer<UrlData<ImageUrlBean.UrlBean>>() { |
其中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 | //MyActivity中 |
我们创建 ViewModelProvider 对象并传入了 this 参数,即所在的MyActivity,然后通过 ViewModelProvider#get方法,传入 MyViewModel 的 class 类型,然后拿到了 MyViewModel 实例 myViewModel。看看ViewModelProvider的构造方法:
1 | // ViewModelProvider.java |
ViewModelProvider的构造函数,即[注释6]的ViewModelStoreOwner是什么?明明我们在 [注释5]传入的是this,即MyActivity。其实,MyActivity继承于ComponentActivity,ComponentActivity 类又实现了ViewModelStoreOwner 接口。所以[注释7]调用的owner.getViewModelStore()就是ComponentActivity类中的getViewModelStore()所返回的ViewModelStore 实例。同时,[注释7]的这个this()方法,就对应了ViewModelProvider的另一个构造函数:
1 | // ViewModelProvider.java |
所以[注释7]ComponentActivity类中的getViewModelStore()所返回的ViewModelStore 实例,最终是存放到了[注释8],ViewModelProvider类中的mViewModelStore变量里。
get()方法:
1 | // ViewModelProvider.java |
获取[注释5]传入的”MyViewModel::class.java” 的 CanonicalName , 调用了另一个 get 方法:
1 | // ViewModelProvider.java |
从put()和get(),mViewModelStore应该是一个map对象,可以通过key值找到对应的value:
1 | // ViewModelProvider.java中mViewModelStore 的定义 |
在[注释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 | // ComponentActivity.java |
关键看看[注释11],当mViewModelStore为null时,会调用 getLastNonConfigurationInstance() 获取 NonConfigurationInstances 对象 nc,当nc不为null时,赋值nc.viewModelStore给mViewModelStore。也就是说,之前创建的 viewModelStore 对象被缓存在 NonConfigurationInstances 中。也就是说,我们可以通过[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象。
那为什么可以通过[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象呢?这是因为Activity 在因配置更改而销毁重建过程中会先调用 onRetainNonConfigurationInstance()方法:
1 | /** |
在[注释12-13],会将现有的mViewModelStore进行缓存.当Activity重建后,就可以调用[注释11]getLastNonConfigurationInstance()获取之前创建的 viewModelStore 对象了。onRetainNonConfigurationInstance 方法和 getLastNonConfigurationInstance 是成对出现的。
“为什么Activity旋转屏幕后ViewModel可以恢复数据”?
因为Activity 因屏幕旋转而销毁时,mViewModelStore对象在onRetainNonConfigurationInstance()方法中缓存了下来,直到Activity重建后,通过getLastNonConfigurationInstance()重新获取出来。
什么时候 ViewModel#onCleared() 会被调用
我们看看4.2小节,ViewModelStore的代码中的[注释14]就有
1 | // ViewModelStore的定义在ViewModelStore.java类中 |
最终在[注释15]vm.clear(),就调用了ViewModel的clear()方法:
1 | public abstract class ViewModel { |
而ViewModel的clear()方法最终会执行[注释16]onCleared()。因此什么时候执行 ViewModel#onCleared() ,就看什么时候执行 ViewModelStore 的 clear() 方法。
1 | // ComponentActivity.java |
在[注释17-18],只要当前Activity处于ON_DESTROY状态且isChangingConfigurations() 返回 false,即当前Activity销毁不是因为 Configuration 的改变而被销毁的。就执行[注释19]getViewModelStore().clear()。进而执行 ViewModel#onCleared() 。