Android架构组件ViewModel源码分析

本文主要从源码角度介绍架构组件ViewModel的实现原理,默认读者已经熟悉MVVM设计模式。

功能与使用场景

在分析原理之前,简单的过一遍功能以及结合具体场景介绍实现原理会更容易理解。

主要功能

在MVVM的模式中,ViewModel的作用在于维护数据与发送消息。但是当前要讨论的架构组件ViewModel并没有提供这两个功能的支持,需要我们自己去继承然后实现这些功能。

架构组件ViewModel实际上提供的功能是:

  • 控制ViewModel的生命周期,使之与Activity与Fragment的生命周期绑定。
  • 虽然与Activity的生命周期绑定,但可以做到不随着屏幕的旋转而销毁。
  • 提供便利的获取ViewModel的方法,使得ViewModel可以更便利的在不同的View类中被访问。
使用场景

一个Activity中有两个View,这两个View需要共享同一个ViewModel,此时 用这个架构组件来实现MVVM中的ViewModel就会比较容易完成模型共享的需求。

整体结构图

相关的类不多,类结构图如下:

image-20181013184503462

简单介绍一下关键类的作用,ViewModelStore用于存取储ViewModel,ViewModelProvider则负责ViewModel的实例化。ViewModelProviders与ViewModelStores则是获取ViewModelProvider与ViewModelStore的便利类。

源码分析

源码分析会主要围绕几个问题展开,具体如下:

ViewModel如何实例化?

ViewModel实例化的入口在ViewModelProvider的get()方法中,裁剪后的代码如下

1
2
3
4
5
6
7
8
9
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

注意到最终的实现应该在Factory中,这里取其中的一种实现AndroidViewModelFactory为例,其create()方法如下:

1
2
3
4
5
6
7
8
9
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (Exception e) {
}
}
return super.create(modelClass);
}

注意到本质上其实就是利用了反射根据class对象实现实例化。同时也可以发现ViewModel和Factory的类型其实是一一对应的,这样的设计可以方便使用者根据需要扩展ViewModel中的必需字段。例如AndroidViewModel就是相对ViewModel多了Application字段的一种扩展实现。

ViewModel如何存取与回收?

ViewModel的存取是通过ViewModelStore来实现的,下面是ViewModelStore的简略实现:

1
2
3
4
5
6
7
8
9
10
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();

final void put(String key, ViewModel viewModel) {
}
final ViewModel get(String key) {
}
public final void clear() {
}
}

易知ViewModelStore的存取功能本质上是通过HashMap实现的。

再看下ViewModelStore的实例化过程,前面提到过ViewModelStore与Fragment以及Activity都是一一对应的,以Fragment为例,它实现了ViewModelStoreOwner接口的getViewModelStore()方法,持有ViewModelStore实例,并负责向外提供,代码如下:

1
2
3
4
5
6
7
8
9
10
public ViewModelStore getViewModelStore() {
if (this.getContext() == null) {
throw new IllegalStateException();
} else {
if (this.mViewModelStore == null) {
this.mViewModelStore = new ViewModelStore();
}
return this.mViewModelStore;
}
}

因为ViewModelStore与Fragment以及Activity是一一对应的,因此在销毁的时候,我们需要一并回收相关资源,具体的实现也比较简单,就是在onDestory()方法中调用ViewModelStore的clear()方法,不过这里有另外一些细节,下一节再讨论。

总结一下,ViewModel的存取是以ViewModelProvider的接口为入口,最终调用到ViewModelStore完成存取,期间用到的实例均是懒加载实现的单例。

ViewModel为什么可以实现旋转屏幕不销毁?

众所周知,在屏幕旋转或者其他一些系统级设置修改的时候(如系统语言),Activity会被销毁重建。如果这时候ViewModel也被回收,则意味着我们需要重新去获取一遍数据,浪费了资源。Google团队考虑到了这一点,因此ViewModel在这种场景下是不会被回收的,下面来看下具体的实现原理。

ViewModel相关的代码在两个不同的包里,ViewModelProvider等基础类在viewmodel的基础包中,而ViewModelProvider等扩展类则在扩展包中。因此旋转屏幕不销毁也有两套不同的实现。

1、基础包的实现

在基础包的实现中ViewModelStore时存储在FragmentActivity中的,相关的关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class FragmentActivity implements ViewModelStoreOwner {
private ViewModelStore mViewModelStore;

protected void onCreate(@Nullable Bundle savedInstanceState) {
this.mFragments.attachHost((Fragment)null);
super.onCreate(savedInstanceState);
FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null) {
this.mViewModelStore = nc.viewModelStore;
}
// 下面会调用Fragment的相关方法进行数据恢复,逻辑类似这里不再展示
}

@override
protected void onDestroy() {
super.onDestroy();
// 保证在配置回收的时候不调用clear()方法
if (this.mViewModelStore != null && !this.isChangingConfigurations()) {
this.mViewModelStore.clear();
}

this.mFragments.dispatchDestroy();
}

// 这个方法可以再配置改变时保存需要的实例
@override
public final Object onRetainNonConfigurationInstance() {
Object custom = this.onRetainCustomNonConfigurationInstance();
// Fragment的mViewModelStore需要通过Activity实现保存
// FragmentManagerNonConfig之于Fragment就相当于NonConfigurationInstances之于Activity
FragmentManagerNonConfig fragments = this.mFragments.retainNestedNonConfig();
if (fragments == null && this.mViewModelStore == null && custom == null) {
return null;
} else {
FragmentActivity.NonConfigurationInstances nci = new FragmentActivity.NonConfigurationInstances();
nci.custom = custom;
// 在这里存储Activity的mViewModelStore
nci.viewModelStore = this.mViewModelStore;
nci.fragments = fragments;
return nci;
}
}

// viewModelStore相关的数据都会通过这个类进行保存,以免被销毁
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;

NonConfigurationInstances() {
}
}
}

总结一下就是利用onRetainNonConfigurationInstance()方法在旋转屏幕时保存ViewModelStore实例,并在重新创建时重新赋值。

2、扩展包的实现

扩展包的实现重点在于HolderFragment的使用,当我们使用ViewModelProviders去获取ViewModel实例时,会导致ViewModeStore存储在HolderFragment中,而不是我们输入的Activity或Fragment中,这个无视图的HolderFragment用于存储ViewModelStore实例,并且可以做到在旋转屏幕时不被回收,最终实现ViewModel的实例存储。

看下它的关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class HolderFragment extends Fragment implements ViewModelStoreOwner { 
private ViewModelStore mViewModelStore = new ViewModelStore();
private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

public HolderFragment() {
setRetainInstance(true);
}

public static HolderFragment holderFragmentFor(FragmentActivity activity) {
return sHolderFragmentManager.holderFragmentFor(activity);
}

static class HolderFragmentManager {
HolderFragment holderFragmentFor(FragmentActivity activity) {
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm);
if (holder != null) {
return holder;
}
holder = mNotCommittedActivityHolders.get(activity);
if (holder != null) {
return holder;
}
if (!mActivityCallbacksIsAdded) {
mActivityCallbacksIsAdded = true;
activity.getApplication()
.registerActivityLifecycleCallbacks(mActivityCallbacks);
}
holder = createHolderFragment(fm);
mNotCommittedActivityHolders.put(activity, holder);
return holder;
}
}
}

关键点在于setRetainInstance(true)方法的调用,根据文档的提示,该方法可以保证Fragment在旋转屏幕的时候不被销毁。

void setRetainInstance)(boolean retain)

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change).

剩下的逻辑无非是Framgment的add与复用,这里不再赘述。同时上面的代码是以Activity为例介绍的,与Fragment相关的实现也是大同小异,这里不再赘述。

参考资料

android-architecture-components