深入理解 ViewModel
本文主要研究了ViewModel的概念、使用方式和内部实现原理。
1. ViewModel概念
ViewModel 用来管理和存储UI相关的数据,和普通的 data store 不同的是:ViewModel会帮你处理 UI Controller 的生命周期带来的问题。
这里需要解释,为什么我们需要处理 UI Controller的生命周期,不处理会有什么问题呢?
-
数据需要在 UI Controller destroyed 后清理,防止内存泄露。
-
数据需要处理 UI Controller 的生命周期。举个例子,Activity 在 configuration changes 后(旋转屏幕等),系统会销毁之前的Activity,并重建一个Activity 实例。这时数据如何在这两个Activity实例之间保持一致。
没有ViewModel,解决问题的思路:
- 使用 static data holder(需要处理lifecycle)
- 将数据持久化
- 持久化的位置——本地、云端?
- 持久化时机——即时、lifecycle callbacks?
ViewModel号称解决了上述问题,它是怎么做到的呢?
2. ViewModel使用
下面先提供个demo:
1 . MyViewModel.java
使用 ViewModel 很简单,直接继承 ViewModel 就好了:
public class MyViewModel extends ViewModel { |
2 . MainActivity.java
和 UI Controller 绑定:
|
一般这样就足够了,不过,工厂方法ViewModelProviders默认使用 ViewModel 的无参构造函数。如果需要指定构造函数,还需要自己实现Factory接口的create方法。
3 . MyViewModelFactory.java
public class MyViewModelFactory implements ViewModelProvider.Factory { |
MyViewModel.java
public class MyViewModel extends ViewModel { |
使用姿势变成:
@Override |
另外,还可以通过传入同一个getActivity(),使得ViewModel成为Activity的多个Fragment之间数据传递层。
需要注意的是,不要在ViewModel中持有 UI Controller 或者 Context,这是因为ViewModel的寿命长于 UI Controller 从而会导致内存泄露。
3. ViewModel优点
- ViewModel 数据层 和 UI Controller 之间分离的很干净。UI Controller 不用负责获取数据,也不用在重建时负责数据的有效性。
- ViewModel 数据层能感知到 UI Controller 的生命周期:保证 UI Controller 重建后,持有的是同一个ViewModel数据实例; UI Controller 结束生命周期后,系统自动调用ViewModel的
clear(),释放资源。 - 配合 LiveData 使用效果更佳。
- 之前放到
onSaveInstanceState()的复杂数据,现在可以放到ViewModel(系统UI相关的除外) - 由于职责划分更加清晰,测试更方便。
4. drive deeper
ViewModel 是如何实现在 UI Controller 重建后维持同一个实例的呢?
回答这个问题之前,先补充一点背景知识。
Fragment.java 里有一个方法:
public void setRetainInstance(boolean retain) { |
这个方法的注释如下:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy()will not be called (butonDetach()still will be, because the fragment is being detached from its current activity).onCreate(Bundle)will not be called since the fragment is not being re-created.onAttach(Activity)andonActivityCreated(Bundle)will still be called.
上面这段话体现在Fragment生命周期上如图所示:

注意,在官方图的基础上加的红线部分就是设置了setRetainInstance之后的生命周期变动。
Fragment在设置setRetainInstance(true)后,当 host Activity re-creation 时,fragment不会被destroyed,而是keep在内存中。当re-creation时,fragment 跳过 onDestroy() 和 onCreate()生命周期,并重新执行一遍 onAttach() 和 onDetach()之间的回调。
因此在 Activity re-creation 后,fragment还是原来的那个实例。
看完这个介绍,是不是有所启发了?
回到 ViewModel 的实现来。
下面大图是 Google Android Architecture Components 的部分框架(图来自Joe Birch),

红色框线部分是ViewModel的部分。

可见ViewModel主要构成部分如下:
ViewModelProvidersViewModelProviderViewModelStoresViewModelStoreViewModel
其中,主角 ViewModel 是一个只有onCleared方法的抽象类
public abstract class ViewModel { |
上述问题的答案显然不在这里。
下面从调用入口开始看:
MyViewModel viewModel = ViewModelProviders.of(this, factory).get(MyViewModel.class); |
获取ViewModel 两个步骤:
- 通过
ViewModelProviders.of()返回的ViewModelProvider - 再通过
ViewModelProvider.get()获取ViewModel实例。
ViewModelProviders 是创建ViewModelProvider的工具类,通过of()方法获取ViewModelProvider

贴of代码(其一):
|
上述代码简单的调用构造函数创建了ViewModelProvider。
而ViewModelProvider 又是创建ViewModel的工具类,提供了get方法获得 ViewModel 。
|
由上面代码可以看到,构造函数传入的ViewModelStore(ViewModelStores.of() 的返回值) 这里充当了ViewModel缓存的功能,Factory则直接负责ViewModel的创建。
ViewModelStores和ViewModelStore的关系也是类似的,ViewModelStores是创建ViewModelStore的工具类。
ViewModelStores也有几个of方法。

public static ViewModelStore of(FragmentActivity activity) { |
在上述代码的具体实现中,注意holderFragmentFor这里,大概猜到了具体实现和Fragment有关。
继续深入。
holderFragmentFor是HolderFragment的类方法,参数为ViewModelProviders.of()传入的activity实例(或者fragment),返回一个HolderFragment实例。内部实现则是委托给了HolderFragmentManager。
(RestrictTo.Scope.LIBRARY_GROUP) |
ViewModelStores.of() 调用HolderFragment的getViewModelStore(),取出其内部持有ViewModelStore,并作为返回值返回。
HolderFragment.java 其他的代码不贴,只看构造函数就行。
public HolderFragment() { |
setRetainInstance(true) 保证了HolderFragment在Activity重建时不会被销毁,在这个基础上,HolderFragment 保证了ViewModelStore在 Activity 重建之后维持同一个实例。
至于ViewModelStore的实现,很简单,不到50行代码:
public class ViewModelStore { |
取得ViewModelStore之后,通过 get(key) 最终获得了一个ViewModel。
那么,HolderFragmentManager又是怎么将 UI Controller 和 HoldFragment关联起来的呢?
第一次获取ViewModel时,HolderFragmentManager创建HolderFragment,并将其添加到 activity state。
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { |
当 Activity 重建后,第二次获取ViewModel时,HolderFragmentManager 内部通过调用FragmentManager.findFragmentByTag来查找之前commit的HolderFragment,由于HolderFragment 不会在Activity重建时销毁,所以这里返回的是同一个HolderFragment实例。
private static HolderFragment findHolderFragment(FragmentManager manager) { |
总结HolderFragmentManager的存储关系如下图:

一个 UI Controller 对应一个HolderFragment。两者之间是通过FragmentManager.findFragmentByTag互相关联起来的。
HolderFragment 持有ViewModelStore实例。而 ViewModelStore 内部通过String key对应着多个ViewModel。
其实很早很早以前 Android 社区就流行使用没有UI的 Fragment 来做为 MVC 中的 Controller。这种Fragment 被称为Headless Fragment,其中就利用到了 setRetainInstance(true) 的这个特性。
ViewModel的设计实现,也算是官方承认了Headless Fragments的这种非正统使用模式了。(Fragment的设计者估计做梦也不会想到竟然还有这种操作吧)。
Author: deskid
Link: https://deskid.github.io/2017/07/28/ViewModel/
License: 知识共享署名-非商业性使用 4.0 国际许可协议