本文主要研究了ViewModel的概念、使用方式和内部实现原理。

1. ViewModel概念

ViewModel 用来管理和存储UI相关的数据,和普通的 data store 不同的是:ViewModel会帮你处理 UI Controller 的生命周期带来的问题。

这里需要解释,为什么我们需要处理 UI Controller的生命周期,不处理会有什么问题呢?

  1. 数据需要在 UI Controller destroyed 后清理,防止内存泄露。

  2. 数据需要处理 UI Controller 的生命周期。举个例子,Activity 在 configuration changes 后(旋转屏幕等),系统会销毁之前的Activity,并重建一个Activity 实例。这时数据如何在这两个Activity实例之间保持一致。

没有ViewModel,解决问题的思路:

  1. 使用 static data holder(需要处理lifecycle)
  2. 将数据持久化
    1. 持久化的位置——本地、云端?
    2. 持久化时机——即时、lifecycle callbacks?

ViewModel号称解决了上述问题,它是怎么做到的呢?

2. ViewModel使用

下面先提供个demo:

1 . MyViewModel.java

使用 ViewModel 很简单,直接继承 ViewModel 就好了:

public class MyViewModel extends ViewModel {
public int myInt = 0;
}

2 . MainActivity.java

和 UI Controller 绑定:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
//....
}

一般这样就足够了,不过,工厂方法ViewModelProviders默认使用 ViewModel 的无参构造函数。如果需要指定构造函数,还需要自己实现Factory接口的create方法。

3 . MyViewModelFactory.java

public class MyViewModelFactory implements ViewModelProvider.Factory {

private AppDatabase mAppDatabase;

public MyViewModelFactory(AppDatabase appDatabase) {
mAppDatabase = appDatabase;
}

@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new MyViewModel(mAppDatabase);;
}
}

MyViewModel.java

public class MyViewModel extends ViewModel {
public MyViewModel(AppDatabase appDatabase) {
//...
}
}

使用姿势变成:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ MyViewModelFactory factory = new MyViewModelFactory(database);
+ MyViewModel viewModel = ViewModelProviders.of(this, factory).get(MyViewModel.class);

//....
}

另外,还可以通过传入同一个getActivity(),使得ViewModel成为Activity的多个Fragment之间数据传递层。

需要注意的是,不要在ViewModel中持有 UI Controller 或者 Context,这是因为ViewModel的寿命长于 UI Controller 从而会导致内存泄露。

3. ViewModel优点

  1. ViewModel 数据层 和 UI Controller 之间分离的很干净。UI Controller 不用负责获取数据,也不用在重建时负责数据的有效性。
  2. ViewModel 数据层能感知到 UI Controller 的生命周期:保证 UI Controller 重建后,持有的是同一个ViewModel数据实例; UI Controller 结束生命周期后,系统自动调用ViewModel的clear(),释放资源。
  3. 配合 LiveData 使用效果更佳。
  4. 之前放到onSaveInstanceState()的复杂数据,现在可以放到ViewModel(系统UI相关的除外)
  5. 由于职责划分更加清晰,测试更方便。

4. drive deeper

ViewModel 是如何实现在 UI Controller 重建后维持同一个实例的呢?

回答这个问题之前,先补充一点背景知识。

Fragment.java 里有一个方法:

public void setRetainInstance(boolean retain) {
mRetainInstance = 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 (but onDetach() 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) and onActivityCreated(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主要构成部分如下:

  • ViewModelProviders
  • ViewModelProvider
  • ViewModelStores
  • ViewModelStore
  • ViewModel

其中,主角 ViewModel 是一个只有onCleared方法的抽象类

public abstract class ViewModel {
protected void onCleared() {
}
}

上述问题的答案显然不在这里。

下面从调用入口开始看:

MyViewModel viewModel = ViewModelProviders.of(this, factory).get(MyViewModel.class);

获取ViewModel 两个步骤:

  1. 通过ViewModelProviders.of()返回的ViewModelProvider
  2. 再通过ViewModelProvider.get()获取ViewModel实例。

ViewModelProviders 是创建ViewModelProvider的工具类,通过of()方法获取ViewModelProvider

of代码(其一):

@MainThread
public static ViewModelProvider of(@NonNull Activity activity, @NonNull Factory factory) {
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}

上述代码简单的调用构造函数创建了ViewModelProvider

ViewModelProvider 又是创建ViewModel的工具类,提供了get方法获得 ViewModel

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
} else {
if (viewModel != null) {
// TODO: log a warning.
}
}

viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

由上面代码可以看到,构造函数传入的ViewModelStoreViewModelStores.of() 的返回值) 这里充当了ViewModel缓存的功能,Factory则直接负责ViewModel的创建。

ViewModelStoresViewModelStore的关系也是类似的,ViewModelStores是创建ViewModelStore的工具类。

ViewModelStores也有几个of方法。

public static ViewModelStore of(FragmentActivity activity) {
return HolderFragment.holderFragmentFor(activity).getViewModelStore();
}

在上述代码的具体实现中,注意holderFragmentFor这里,大概猜到了具体实现和Fragment有关。

继续深入。

holderFragmentForHolderFragment的类方法,参数为ViewModelProviders.of()传入的activity实例(或者fragment),返回一个HolderFragment实例。内部实现则是委托给了HolderFragmentManager

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
return sHolderFragmentManager.holderFragmentFor(activity);
}

ViewModelStores.of() 调用HolderFragmentgetViewModelStore(),取出其内部持有ViewModelStore,并作为返回值返回。

HolderFragment.java 其他的代码不贴,只看构造函数就行。

public HolderFragment() {
setRetainInstance(true);
}

setRetainInstance(true) 保证了HolderFragment在Activity重建时不会被销毁,在这个基础上,HolderFragment 保证了ViewModelStore在 Activity 重建之后维持同一个实例。

至于ViewModelStore的实现,很简单,不到50行代码:

public class ViewModelStore {

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

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

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

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

取得ViewModelStore之后,通过 get(key) 最终获得了一个ViewModel

那么,HolderFragmentManager又是怎么将 UI Controller 和 HoldFragment关联起来的呢?

第一次获取ViewModel时,HolderFragmentManager创建HolderFragment,并将其添加到 activity state。

private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
HolderFragment holder = new HolderFragment();
fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
return holder;
}

当 Activity 重建后,第二次获取ViewModel时,HolderFragmentManager 内部通过调用FragmentManager.findFragmentByTag来查找之前commit的HolderFragment,由于HolderFragment 不会在Activity重建时销毁,所以这里返回的是同一个HolderFragment实例。

private static HolderFragment findHolderFragment(FragmentManager manager) {
if (manager.isDestroyed()) {
// throw Exception;
}

Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
// check null and type...
return (HolderFragment) fragmentByTag;
}

总结HolderFragmentManager的存储关系如下图:

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的设计者估计做梦也不会想到竟然还有这种操作吧)。