Jetpack源码 之 ViewModel
0. 前言
ViewModel作为MVVM中最重要的一层,他的作用就是对数据状态的持有与维护。
根据源码里面的注释,我们可以知道,它在Android中事实上是为了解决一下两个问题:
- UI组件间实现数据共享
- Activity配置更改重建时保留数据
对于第一条,如果不同VM,那么各个UI组件都需要持有共享数据的引用,这样会带来两个麻烦:
- 如果新增共享数据,则各个UI组件需要再次声明并初始化新增的共享数据
- 某个组件对于数据的修改,没办法直接通知其他UI组件,需手动实现观察者模式
对于第二条,如果不使用VM,那么还是可以通过onSaveInstanceState保存的,但是如果数据量比较大,数据的序列化和反序列化都会产生一定的性能开销。
所以我们看ViewModel的源码,就需要从这两个问题入手:
- ViewModel是如何解决UI组件间共享数据的
- ViewModel是怎么解决重建时保留数据的
1. 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69public abstract class ViewModel {
private final Map<String, Object> mBagOfTags = new HashMap<>();
// 用于标记当前ViewModel是否已经不再被使用
private volatile boolean mCleared = false;
// 这个方法会在ViewModel不再被使用并且即将被销毁时调用
protected void onCleared() {
}
// 注释里面写到:
// 由于clear()方法是final的,所以这个方法仍然会被在模拟对象中被调用,
// 并且在这种情况下,mBagOfTags为null
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
// 当当前key不存在时才将数据保存进去
// 类似于ConcurrentHashMap的 putIfAbsent()
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
// 当mCleared时,异常
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
<T> T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
/**
* 用于关闭
*/
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
上面基本上对ViewModel这个抽象类有了一个大致的了解,那接下来我们来看下ViewModel是如何被创建的。
2. ViewModel的创建
一般情况下,我们都会通过这个代码创建ViewModel:1
mViewModel = ViewModelProvider(this).get(MvvmViewModel::class.java)
这里的this要求传入ViewModelStoreOwner,一般我们的Activity和Fragment都实现了这个接口。我们来看下这个ViewModelProvider的构造方法:
2.1 ViewModelProvider
1 | public ViewModelProvider( { ViewModelStoreOwner owner) |
我们调用的是第一个构造方法,这个方法中调用了第三个构造方法,然后通过getViewModelStore()
获取到了ViewModelStore,并判断我们这个owner
是不是HasDefaultViewModelProviderFactory子类,如果是就调用HasDefaultViewModelProviderFactory的getDefaultViewModelProviderFactory()
否则调用NewInstanceFactory.getInstance()
。
这块还是别看AndroidStudio自带的SDK的源码了,这个时候最新的源码是API30,但是ComponentActivity并没有实现HasDefaultViewModelProviderFactory,而从官方Github看到的源码是已经实现了的。
而我们的Activity和Fragment都是实现了这个接口的。
他们的getDefaultViewModelProviderFactory()
最后都创建了这样一个东西:1
2
3
4mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
那我们再来看下get()
方法:
2.1 ViewModelProvider # get()
1 |
|
到这块,我们的创建ViewModel就说完了,最后总结下:
- ViewModelStore是用来保存ViewModel对象的HashMap,Activity和Fragment都会构造这个的对象;
- ViewModelProvider中会通过
get()
方法创建一个ViewModel,创建之前会检测ViewModelStore中有没有缓存了的,如果有直接返回,没有就通过反射去创建。
3. 重建时如何保证ViewModel不会重建
了解到VieModel的创建后,我们现在来看第一个问题:ViewModel是怎么解决重建时保留数据的?
我们回顾下刚刚创建的ViewModel的流程,在get()
方法中主要是通过ViewModelStore来帮我们保存ViewModel的,那么ViewModelStore是怎么创建的?
3.1 ViewModelStore的创建
3.1.1 Activity中
在ComponentActivity中有getViewModelStore()
这样的一个方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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) {
// 通过NonConfigurationInstances获取到ViewModelStore
mViewModelStore = nc.viewModelStore;
}
// 如果获取不到,则新建一个ViewmodelStore
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
这块就涉及到了一个NonConfigurationInstances,而熟悉Activity重建机制的小伙伴应该会很熟悉这个,这个就是与我们的重建机制有关。
当我们需要重建Activity的时候,除了通过onSaveInstanceState()
保存数据之外,也可以通过onRetainNonConfigurationInstance()
这个方法:
ComponentActivity # onRetainNonConfigurationInstance()
1 | public final Object onRetainNonConfigurationInstance() { |
那么onRetainNonConfigurationInstance()什么时候回被调用呢
在Activity的启动流程中,当ActivityThread执行到performDestroyActivity()
这个方法时,就会调用Activity的retainNonConfigurationInstances()
方法将保存的数据保存到ActivityClientRecord中:1
2
3
4
5
6
7
8
9
10
11
12
13NonConfigurationInstances retainNonConfigurationInstances() {
// 调用了 onRetainNonConfigurationInstance() 获取到 NonConfigurationInstances
Object activity = onRetainNonConfigurationInstance();
// ...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
// ...
return nci;
}
可以看到他先调用onRetainNonConfigurationInstance()
获取到ComponentActivity返回来的nci
,然后由构建了一个nci
,并将之前ComponentActivity的那个nci
保存到了这个nci
的activity
中。
ActivityThread # performDestroyActivity() 保存nci
1 | ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, |
那么什么时候恢复呢
当页面重构完成,就会调用ActivityThread的performLaunchActivity()
:1
2
3
4
5
6
7
8
9
10private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
// ...
return activity;
}
Activity # attach()
1 | final void attach(Context context, ActivityThread aThread, |
这块就将值赋给了Activity的mLastNonConfigurationInstances
。
然后我们在回到3.1.1节的getViewModelStore()
方法中,如果调用getLastNonConfigurationInstance()
返回的nc
不为null的话,就直接取出他的viewModelStore
,这样我们不就实现了Activity重建但是ViewModel仍然不会重建的问题嘛。
3.1.2 Fragment中
一样的,也来看下Fragment的getViewModelStore()
方法1
2
3
4
5
6public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
可以看到他里面调用了FragmentManager的getViewModelStore()
方法:
FragmentManagerImpl # getViewModelStore()
1 | ViewModelStore getViewModelStore( { Fragment f) |
这里面直接调用了mNonConfig的getViewModelStore方法。
那么这个mNonConfig是个啥呢?
1 | private FragmentManagerViewModel mNonConfig; |
就是一个FragmentManagerViewModel,那么他是在哪赋值的:
FragmentManagerImpl # attachController()
1 | public void attachController( FragmentHostCallback host, |
这个方法中就是根据不同的分支返回不同的mNonConfig,而这个方法则会被FragmentController的attachHost调用:
FragmentController # attachHost()
1 | public void attachHost( { Fragment parent) |
而这个方法接着会被FragmetActivity中的onCreate调用。
FragmentActivity # onCreate()
1 |
|
而attachHost中的那个host则是在FragmentController构造方法中传入的:1
2
3
4
5
6
7
8
9private FragmentController(FragmentHostCallback<?> callbacks) {
mHost = callbacks;
}
public static FragmentController createController( { FragmentHostCallback<?> callbacks)
return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
}
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
而这个HostCallbacks则实现了ViewModelStoreOwner:1
2
3class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner {
4. UI组件间数据共享
只要我们在调用ViewModelProvider(this).get(MvvmViewModel::class.java)
时传入的this是同一个,那么就能获取到同一个ViewModelStore,对应的返回的ViewModel也是同一个了。