在学习viewModel之前,我们需要先了解MVC、MVP 和 MVVM 架构模式。
简单的了解可以查看MVC,MVP 和 MVVM 的图示。
另超级好文:Android App的设计架构:MVC,MVP,MVVM与架构。
MVC
图示:
是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
Android中界面部分也采用了当前比较流行的MVC框架,在Android中:
视图层(View) :一般采用XML文件进行界面的描述。
控制层(Controller): Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
模型层(Model) :我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。
MVC的缺点: 在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。
MVP
在App开发过程中,经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。因此引入MVP框架。MVP图示如下:
MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
View:负责绘制UI元素(一般包括Activity,Fragment,Adapter等直接和UI相关的类)、与用户进行交互(在Android中体现为Activity)
Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
View interface: 需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试
View interface的必要性:回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用 户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。
当我们将Activity复杂的逻辑处理移至另外的一个类(Presenter)中时,Activity其实就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由 Presenter处理)。
MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Activty中包含的大量逻辑操作放到控制层中,避免Activity的臃肿。
两种模式的主要区别:
(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。
因此我们可以发现MVP的优点如下:
1、模型与视图完全分离,我们可以修改视图而不影响模型;
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。
在MVP中,Activity的代码不臃肿;
在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;
在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。
MVVM
MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。其图示如下:
MVVM模式将Presener改名为View Model,基本上与MVP模式完全一致,唯一的区别是,它采用双向绑定(data-binding),View的变动,自动反映在 ViewModel,反之亦然。
所以这里也就出现了ViewModel。
ViewModel
在Android中的MVVM出现是Jetpack系列,主要使用LiveData、ViewModel和Data-binding结合实现MVVM。
我们知道类似旋转屏幕等配置项改变会导致我们的 Activity 被销毁并重建,此时 Activity 持有的数据就会跟随着丢失,而ViewModel 则并不会被销毁
,从而能够帮助我们在这个过程中保存数据,而不是在 Activity 重建后重新去获取。并且 ViewModel 能够让我们不必去担心潜在的内存泄露问题
,同时 ViewModel 相比于用onSaveInstanceState()
方法更有优势,比如存储相对大的数据,并且不需要序列化以及反序列化。
ViewModel的生命周期图为:
从上图中可以看到 ViewModel 在 Activity 的重建时依然存活。为什么?
使用ViewModel主要的业务场景为:
我们旋转屏幕而没有其他处理的时候,因为屏幕旋转activity
被销毁重建,存放在activity
的数据自然就会丢失了。而如果使用ViewModel就不会出现这种情况。
比如新建一个MyViewModel.java,内容如下:
public class MyViewModel extends ViewModel {
public int number = 0;
}
在主Activity中的代码如下:
public class MainActivity extends AppCompatActivity {
Button button;
MyViewModel myViewModel;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);
myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
textView.setText(String.valueOf(myViewModel.number));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myViewModel.number++;
textView.setText(String.valueOf(myViewModel.number));
}
});
}
}
就可以实现在屏幕翻转的时候,数据不会丢失。
那么ViewModel是如何实现的?
ViewModel内部实现
and ViewModel怎么更新数据的。
myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
通过ViewModelProvider实例的get方法来得到自己所需要的ViewModel实例对象。
那么myViewModel.number++;操作的时候,是如何完成更新和保存的?不妨先看下我们的ViewModel对象是如何得到的:
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);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
在viewModel实例化后,使用ViewModelStore来进行存储。当首次创建的时候使用NewInstanceFactory,如下:
public static class NewInstanceFactory implements Factory {
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
可以看到这里是反射来得到的实例对象。然后通过mViewModelStore.put(key, viewModel);
将该对象进行存储,put实现如下:
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
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);
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
在ViewModelStore类中,使用HashMap进行存储,key为前面提到的DEFAULT_KEY + ":" + canonicalName
,也就是DEFAULT_KEY + ":"
加全限定类名。
到此,我们知道了ViewModel是如何得到实例对象,以及如何存储的了。但是还是不知道该对象是如何进行更新数据的。所以我们需要更加仔细些。
我们重新观察:
new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
前面就只剩下ViewModelProvider类的这个构造方法没有了解,那么不妨看下:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
也就是说关键在于ViewModelStore是如何得到的。也即是说ViewModelStoreOwner类是如何书写的。注意到:
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
ViewModelStoreOwner是一个接口,且传入的是this对象,那么在Activity的某个父类中必然做了相应的实现。很容易可以追踪到在FragmentActivity中实现了这个接口:
public class FragmentActivity extends SupportActivity implements ViewModelStoreOwner
那么我们可以找下这个接口对应的getViewModelStore方法的实现:
public ViewModelStore getViewModelStore() {
if (this.getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
} else {
if (this.mViewModelStore == null) {
FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null) {
this.mViewModelStore = nc.viewModelStore;
}
if (this.mViewModelStore == null) {
this.mViewModelStore = new ViewModelStore();
}
}
return this.mViewModelStore;
}
}
不存在就使用ViewModelStore的无参数构造进行实例化。
一件比较有趣的事情就是,我在追踪viewModel类的时候,发现了这里给了一个使用案例,和我使用的并不一样:
public class MyFragment extends Fragment {
public void onStart() {
UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
}
}
使用的是ViewModelProviders,而不是ViewModelProvider。然后我在MainActivity.java文件中做了相应的替换:
// myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
发现结果居然一样。
注意到我这里的版本为:implementation 'android.arch.lifecycle:extensions:1.1.1'
,原来在2.2.0中ViewModelProviders方法被弃用了。
但是,至于ViewModel怎么更新数据的?这个我确实没有看见。这个问题就以后再思考。