Jetpack学习-ViewModel

什么是ViewModel?

谷歌对ViewModel的介绍是这样的: ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据, ViewModel中数据会一直存活即使 activity configuration发生变化, 比如横竖屏切换的时候.

实际上, 我们可以这么理解ViewModel: 它是一个更高层次的仓库. 这个仓库与创建它的Activity或者Fragment绑定, 当Activity或者Fragment彻底死亡后, 这个仓库才会死亡.

上图是我们经常看到的ViewModel生命周期的一个展示. 可以看到, ViewModel并不是只要Activity进入onDestroy()阶段就会被销毁——只有Activity通过finish进入onDestroy()阶段(也就是调用finish()方法的时候), 即Activity彻底被销毁后, 与之绑定的ViewModel才会被销毁(对于Fragment来说, 是当Fragment进入onDetach()的时候).

ViewModel的使用

因为ViewModel只是一个容器, 它在系统中不是单独出现的, 所以这篇文章并不打算去展示一些场景的代码, 而是围绕着ViewModel的核心特点——一个伴随着创建者一生的容器类这一点, 来讨论什么时候可以使用ViewModel, 使用它的时候应该注意什么.

数据与UI的解耦

针对ViewModel的核心特点, 我们首先能想到的是, 让ViewModel成为一个数据的容器. 在ViewModel出现之前, Activity和Fragment不仅需要控制UI(比如一长串的findViewById以及随之而来的各种setOnClickListener), 还需要保存数据, 这使得作为UI Controller的Activity和Fragment承担了过多的任务, 违背了单一职责原则. 引入ViewModel后, 由于ViewModel的生命周期和Activity或者Fragment基本相同(实际上略长, 因为ViewModel是在观察的Lifecycle销毁后才销毁自己的), 我们可以将数据转存到ViewModel中, 这样虽然逻辑上数据和Activity是在一起的, 但是实际上我们将数据管理的职责分配给了ViewModel, 这样就实现了数据与UI的解耦.

数据生命周期的延长

Activity和Fragment存储数据还会带来另一个问题: 当Activity重新创建时, 之前所存储的数据都会消失, 毕竟保存它们的类已经被销毁过了. 在ViewModel出现之前, 我们都是通过一个Bundle来保存数据, 等Activity或者Fragment调用onCreate时, 再从Bundle中取出数据. 这种方式是有一定局限的——Bundle只能存储基本数据类型和可序列化的类. 现在有了ViewModel, 当Activity或者Fragment销毁重建的时候, 因为ViewModel所观察的Lifecycle并没有被销毁, 所以它仍然存在. 这样新的Activity或者Fragment实例就可以通过ViewModel重新加载之前的数据, 这些数据即使不可序列化也没有关系了.

数据的共享

由于一个ViewModel是伴随着创建者的一生的, 这意味着如果创建者没有彻底销毁, 那么ViewModel应该是始终存在且唯一的, 我们可以把这种特性称为“伪单例”. 从这个性质出发, 我们很容易想到ViewModel可以用来实现数据的共享. 我们来看一下使用ViewModel的代码:

1
ViewModel viewModel = new ViewModelProvider(this).get(ViewModel.class);

这里的”this”, 一般就是Activity或者Fragment的自引用. 从这句话中我们可以看到, 我们能获得什么样的ViewModel, 取决于我们在ViewModelProvider的构造函数中和get方法中传入什么样的值. 换句话说, 只要传的值一样, 最终获取到的ViewModel就是一样的. 这样一来, 如果同一个Activity下的两个Fragment想要共享数据, 那么它们只需要通过与宿主Activity绑定的ViewModel来通信就可以了.

异步请求的处理

这一点可能比较难想到. 一般来说, APP所展示的数据都是我们通过网络向服务器请求来的. 因为安卓是不允许在主线程进行网络请求的, 所以所有的网络通信都是异步的, 这就会带来一个问题: 在极少数情况下可能会出现Acitivty刚刚发出请求后就被销毁的情景. 由于异步请求的结果处理是在Activity上进行的, 所以肯定会持有Activity的引用, 这就使得Activity无法被回收, 造成内存泄漏. 如果将异步请求交由ViewModel进行, 由于ViewModel的生命周期足够长, 这种情况就不会发生了.

**那么, ViewModel就绝对不会引起内存泄漏吗? 当然不是. **我们已经知道, ViewModel的生命周期一般是比Activity和Fragment要长的. 如果ViewModel持有了它们的引用, 自然就会引起内存泄漏. 当然, 一般情况下大家不会直接引用一个Activity或者Fragment. 但是我们要知道, 它们是实现了Context和LifecycleOwner的接口的, 这两个东西我们是会经常用到的, 所以很可能不经意间我们就引用了它们, 为系统带来了内存泄漏的风险.

当然, 谷歌也提供了一个折中的办法: 如果你一定要使用Context, 那么你可以扩展AndroidViewModel类. 这个类是ViewModel的一个扩展, 它的构造函数需要传入Application的引用. 我们知道, ApplicationContext是一个静态单例, 引用它并不会导致内存泄漏(当然, 如果不是必要的, 建议还是不要持有这个引用).

ViewModel的实现原理

ViewModel的存储

从获取ViewModel的那句代码里我们可以直到, 我们获取到的ViewModel是什么, 取决于我们传入的两个参数, 这种模式很像一个Key-Value对. 既然ViewModel是长期存在的, 那么一定有一个数据结构来存储所有ViewModel的子类实例, 我们猜测这个数据结构应该是一个Map.

在找到Map之前, 我们先来了解一下这个Key-Value对. 我们都知道Value是ViewModel的子类实例, 那么Key, 也就是我们传入的两个参数参数到底是什么呢?

ViewModelProvider
1
2
3
4
5
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}

这是对应的ViewModelProvider的构造函数的声明, 我们看到, 第一个参数, 也就是ViewModelProvider的构造函数的参数是一个ViewModelStoreOwner的接口, 显然AppCompatActivity和Fragment都实现了这个接口. 和LifecycleOwner一样, ViewModelStoreOwner实际上也只是标记了一个类是否含有ViewModelStore, 所以我们还要再看一眼ViewModelStore.

ViewModelStore
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
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);
}

Set<String> keys() {
return new HashSet<>(mMap.keySet());
}

/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}

这是ViewModelStore的源码. 不出我们所料, ViewModel确实是用一个Map维护着的. 但是如何确定Map的Key值呢? 那就还需要用到第二个参数, 也就get方法的参数. 再在ViewModelProvider中寻找, 我们很快就找到了这个方法:

ViewModelProvider.get
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
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)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

上面两个get()方法是ViewModelProvider中的两个方法, 通常我们都使用第一个方法来获取ViewModel. 这个get方法通过反射获取传入参数的类名, 再从Owner的ViewModelStore中的Map去寻找对应的ViewModel. 如果Map不存在这个ViewModel, 那么就调用第二个get()方法, 先创建一个这样的ViewModel, 再将其放入Map中, 最后再返回这个ViewModel.

到此, ViewModel的存储原理就很清楚了:

  1. 作为ViewModelStoreOwner的Activity和Fragment拥有着一个ViewModelStore. 这个Store通过Map来存储所有和Activity或者Fragment绑定的ViewModel, Map的Key值由对应的ViewModel的类名再加工而来.

  2. 我们获取ViewModel时, 通过创建一个ViewModelProvider的实例来指定Store和Map的Key值, 通过Key值去对应的Map中获取到对应的ViewModel.

如果你还没有看明白, 那么我再打一个比方:

  1. 一个ViewModelStore好比数据库中的一个数据表.

  2. 数据表中的数据有两个字段, 一个是作为关键字的String类型字段, 另外一个字段就是ViewModel的实例. 关键字的String字段由ViewModel的类名加工而来.

  3. 一个ViewModelProvider实例好比一条SQL语句, 它指明了要在哪张数据表中获取哪条数据.

看到这儿, 我想大家应该对于ViewModel的存储有一个比较好的理解了.

ViewModel的生命周期实现

接下来我们来看一下ViewModel是怎么实现它的生命周期的.

ViewModel的实现和Lifecycle没有什么关系, 它的实现源自于静态内部类.

以AppCompatActivity为例. 我们先向上寻找AppCompatActivity实现ViewModelStoreOwner的父类——ComponentActivity(正是实现了LifecycleOwner的那个父类), 在它的源码中, 我们能找到下面的片段:

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
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}

public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();

ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}

if (viewModelStore == null && custom == null) {
return null;
}

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}

在片段中可以看到, ComponentActivity声明了一个静态内部类: NonConfigurationInstances. 当ComponentActivity的getViewModelStore被调用时, ComponentActivity就认为它存储的ViewModel被改变了, 这时它就会将最新的ViewModelStore存储在NonConfigurationInstances中. 由于静态内部类与外部类是独立的, 即使ComponentActivity被重新创建了, 也不会影响NonConfigurationInstances的内容. ComponentActivity被重新创建后, 会通过onRetainNonConfigurationInstance()方法获取之前存储的ViewModelStore的最新副本, 这样就做到了即使重新创建Activity也不会影响ViewModel的效果. 当Activity的实例被销毁后, 因为再没有实例引用NonConfigurationInstances, 所以它会在一段时间后被回收掉, 这就实现了ViewModel生命周期略长于Activity的效果.