ViewModel 原理

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

如何使用

1
private val viewModel = ViewModelProvider(this).get(MyCustomViewModeClass::class.java)

ViewModelProvider#构造方法

1
2
3
4
5
6
7
8
9
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}

传入一个 ViewModelStoreOwnder 对象,可以是 androidx.activity.ComponentActivityandroidx.fragment.app.FragmentActivity 或者 androidx.fragment.app.Fragment ,因为这几个类都实现了 ViewModelStoreOwner 接口

在这个构造方法中,会调用双参数的构造方法ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory)

第一个参数传入的是 owner.getViewModelStore() 即由实现 ViewModelStoreOwnder 接口的类返回的 ViewModelStore 对象
第二个参数传入的是一个 Factory 对象,是用来创建 viewmodel 的工厂类

ViewModelProvider#get()

接着看 get(@NonNull Class<T> modelClass) 方法

会先通过开发者传入的 modelClass 的 canonicalName 字符串,并拼接上 DEFAULT_KEY 和 「:」作为前缀
先从 mViewModelStore 中判断是否有该 viewmodel 的缓存,如果有则返回缓存
否则,通过 Factory 创建该 viewmodel 后进行缓存,最后返回该 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
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;
}

ViewModelStore

顾名思义,就是存放 ViewModel 的类,很简单的一个类,内部维护了一个 HashMap,以String 为 key,value 为对应的 viewModel ,并对外提供 put()set()keys()clear() 方法对 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
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();
}
}

ViewModel 的恢复

对于 Activity 的配置修改,比方说屏幕旋转等行为,我们知道 Activity 可能被重建,但是为什么 viewmodel 缺没有被销毁而重新实例化,而是能拿到先前的实例对象呢

从上文我们知道 FragmentActivity 和 ComponentActivity 中都实现了 ViewModelStoreOwnder 接口并在 getViewModelStore()方法中返回了 ViewModelStore 对象
很容易理解,如果全局变量 mViewModelStore 为空,则从 getLastNonConfigurationInstance() 中获取 viewModelStore 并赋值给 mViewModelStore,如果此时还为空,则新建一个 ViewModel 对象,赋值给 mViewModelStore 并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

那这个 getLastNonConfigurationInstance() 是什么呢

根据注释可以看到,这个方法返回是 onRetainNonConfigurationInstance() 方法保存的 non-configuration 实例数据,我们接着看 onRetainNonConfigurationInstance() 方法

1
2
3
4
Activity#onRetainNonConfigurationInstance()
public Object onRetainNonConfigurationInstance() {
return null;
}

这个方法由系统调用,子类会 override 这个方法,通过源码可以看到是在 Activity#retainNonConfigurationInstances() 中调用的,而这个方法会把
onRetainNonConfigurationInstance() 的返回值存到 NonConfigurationInstances 对象的 activity 属性中

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
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

// We're already stopped but we've been asked to retain.
// Our fragments are taken care of but we need to mark the loaders for retention.
// In order to do this correctly we need to restart the loaders first before
// handing them off to the next activity.
mFragments.doLoaderStart();
mFragments.doLoaderStop(true);
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

if (activity == null && children == null && fragments == null && loaders == null
&& mVoiceInteractor == null) {
return null;
}

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}

当 Activity 的配置发生变化,例如屏幕旋转后,会创建一个新的 Activity 实例,调用 ActivityThread#performDestroyActivity() 方法销毁旧的 Activity,
该方法中会调用 retainNonConfigurationInstances() 方法,将其返回的 NonConfigurationInstances 对象存放到 ActivityClientRecordlastNonConfigurationInstances 属性中,在启动下一个 Activity 的时候,会将该值赋予下一个 Activity,于是通过 getLastNonConfigurationInstance() 方法就可以获取到 retainNonConfigurationInstances() 保存的数据

再回到刚才的 onRetainNonConfigurationInstance() 方法,这个方法需要由子类 override 实现需要保存的数据

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
@Override
@Nullable
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;
}

可见这里会将 mViewModelStore 对象进行保存到 NonConfigurationInstances 的实例中并返回,于是下次重建的时候即可拿到上次的实例,从而达到 viewmodel 为同个实例的目的

流程图

viewmodel_restore.jpg

疑问😳

那为什么正常关闭的 Activity 缺不会使用上一个同个类型的 Activity 的 ViewModel 呢?

我们从 ViewModelStore#clear()方法看起

通过调用方法链可以看到 clear() 被调用的地方为 ComponentActivity 的构造方法,也就是当 Activity Destroy 后会清除掉该 Activity 实例的 ViewModelStore 中存储的 viewmodel,但是这里有个判断条件 !isChangingConfigurations() 也就是说只有非因为配置变化而导致 Activity 被 destroy 的情况下才会调用 viewmodelstore 的 clear 方法

1
2
3
4
5
6
7
8
9
10
11
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();
}
}
}
});

参考

ViewModel 概览

作者

PPTing

发布于

2022-03-19

更新于

2022-03-19

许可协议

评论