1.什么是ViewModel
对于Android传统的代码编写方式,一般地,将页面UI的处理,数据的加载,全部放在Activity或Fragment中进行,但这并不满足“单一功能原则”,也不易于维护和扩展。我们应该将项目结构进行分层,传统的MVC,MVP和MVVM,都是将项目结构分了三层,“各管一摊”,这三种模式各有特点、各有利弊,但它们都有一个共同点,就是区分出了M层与V层,M即Model层,V即View层,M层负责数据的处理,View层负责UI的展示,不同的地方在于如何将M层与V层进行结合。
其中,MVVM模式除了M层和V层之外,就是VM层,即ViewModel。
Jetpack为开发者提供了ViewModel的概念,将页面所需要的数据从V层和M层中剥离出来,ViewModel是介于View层和Model层的一个桥梁,使得视图和数据即区分开来,又能保持联系。在 Android 中,ViewModel 的作用就是在 UI 控制器(如 Activity、Fragment)的生命周期中保存和管理 UI 相关的数据。ViewModel 保存的数据在配置更改(如屏幕旋转)后会依然存在,不会丢失。
2.ViewModel的使用场景
- 防止因屏幕旋转导致 Activity 重建而丢失数据
- 同一 Activity 中不同 Fragment 间数据共享
- 防止销毁的 Activity 中未完成的异步回调
3.ViewModel的生命周期
当Android应用程序退回到桌面,或者横竖屏切换时,Activity等组件可能会丢失状态或者是被销毁,这时,开发者通常需要考虑数据的保存和恢复,常见的就是通过onSavaInstanceState()方法和onRestoreInstanceState()方法来实现,有了ViewModel,就可以用更简单的方法来保存数据了。这是为什么呢?
ViewModel独立于组件的配置的变化,也就是说,当发生特殊情况导致Activity重新执行某些生命周期时,ViewModel的生命周期并不会发生变化。
下图是ViewModel与Activity的生命周期的对应关系:
4.源码分析
4.1.为什么 ViewModel 可以防止因屏幕旋转导致 Activity 重建而丢失数据
获取 ViewModel 实例对象需要通过如下代码:
val model = ViewModelProvider(this).get(ShareViewModel::class.java)
在 ViewModelProvider 的构造函数中,主要是为 ViewModelStore、Factory 两个变量赋值。
先看下 ViewModelStore,其中包含一个 HashMap 存储 ViewModel,还有put()、get()、clear() 函数,put 和 get 函数都是在 ViewModelProvider 的 get 函数中调用,clear 函数是在 ComponentActivity 的构造函数中调用。
public ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
这里看到调用 clear 函数的前提非改变配置导致的页面重建 。这就解释了为什么 ViewModel 可以防止因屏幕旋转导致 Activity 重建而丢失数据了。
4.2.同一Activity 中不同 Fragment 间数据共享
不同 Fragment 间数据共享的重点是,获取他们所共同依赖的 Activity 中保存的 ViewModelStore,只有这样才可以得到 ViewModel 的同一实例对象,再配合 LiveData 即可实现数据变化的监听。
4.3.防止销毁的 Activity 中未完成的异步回调
其实来说 ViewModel 自身的功能只有两个
- 防止因屏幕旋转导致 Activity 重建而丢失数据。
- 同一 Activity 中不同 Fragment 间数据共享。
防止销毁的 Activity 中未完成的异步回调,这个功能是配合 LiveData 实现的。异步回调结果修改 LiveData 数据,通过观察者监听 LiveData 数据的修改更新 UI 显示,在 LiveData 篇章中可以知道当页面销毁时观察者删除,如此防止调用销毁页面的 UI。