Android Jetpack 架构组件之 ViewModel 源码分析
1. 认识 ViewModel
1.1. ViewModel 是什么
引用官方介绍:
ViewModel 被设计成以生命周期感知的方式去存储和管理和 UI 相关的数据。ViewModel 类允许诸如屏幕旋转等配置改变之后继续保存数据。它使得从 UI Controller (Activity 或 Fragment)将 View 的数据分离出来变得更加的容易且高效。
1.2. ViewModel 的优势
- 职责单一,主要是存储和对 UI 数据进行管理。
- 具有生命周期感知能力,当 Activity 的 ON_DESTROYED 生命周期事件被分发了,并且不是系统配置发生改变的情况下,才会销毁当前 页面中 ViewModelStoreOwner 所有 ViewModel 对象。
- 支持 coroutine。
- 支持同一个 Activity 下的多个 Fragments 页面之间共用同一个 ViewModel,并使用 LiveData 来进行数据的共享。
- 能够和 LiveData,Room 配置来实现非常优雅的数据库操作。
1.3. 传统数据管理存在的问题
在以往的操作中,我们一般会把数据直接写在页面对象中(如:Activity,Fragment),而当 configuration 配置发生改变的时候,页面对象被重建,之前缓存在业务的数据已经被回收。如果我们要保存数据的话,需要在 onSaveInstanceState()
回调方法中进行数据的保存,然后,在重建的 Activity 的 onCreate()
回调方法中进行数据的huifu恢复。这显然会增加开发人员的工作量,而且稍不注意就会漏写。
又或者在以往的 MVP,MVVM 等架构中将数据保存在了 Presenter 层或者 ViewModel 层(非官方提供的),而当 Activity 由于屏幕旋转等被销毁重建后,如果没有做特殊的处理,被重建的 Activity 又会去绑定新的 Presenter 或 ViewModel 对象,等于说还是没有很好的起到保存数据的作用。
2. ViewModel 结构及各自职责
2.1. ViewModel 类关系图
2.2. ViewModel 各自职责
根据上图我们来对其中的主要的几个类在整个实现中所起到的作用做一个说明。
1. ViewModelProvider
public class ViewModelProvider {
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey";
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
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;
}
......
ViewModelProvider 其实是一个 ViewModel 的管理类,主要负责存储和获取 ViewModel 对象。所有的 ViewModel 对象都是通过这个类来进行获取的。ViewModelProvider 依赖于两个对象,一个是 ViewModelStore 对象,用于实际存储 ViewModel 对象,另外一个是具体创建 ViewModel 的工厂类 Factory 及其实现。
其核心的方法只有一个就是 get(@NonNull String key, @NonNull Class<T> modelClass)
方法,用于通过 ViewModel 的 Key 来获取到当前页面所对应的 ViewModelStore 中所存储的 ViewModel 对象。
ViewModelProvider 这里其实没有并没有将 ViewModel put 进去的方法,你只需要通过实现了 ViewModel 的 Class 类对象直接来获取就可以了,如果在缓存中没有找到,就会直接将其装入缓存并将其 ViewModel 的实例对象返回。
另外,这里还有一个 get(@NonNull Class<T> modelClass)
方法,当我们在获取 ViewModel 的过程中,没有指定 Key 的话,ViewModelProvider 会为我们设置一个默认的可以唯一区别于其它 ViewModel 的 key 作为默认的 key。
还有一点是,这里对 ViewModel 的类对象作了严格的限定,必须是具有 canonical name
的类对象。哪些类对象是没有 canonical name
的呢?主要是:内部类,匿名内部类和数组。后两种基本上不会碰到,但是内部类还是有可能出现的,所以,在开发的时候要注意一下。
2. SavedStateViewModelFactory
public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {
private final Application mApplication;
private final ViewModelProvider.AndroidViewModelFactory mFactory;
private final Bundle mDefaultArgs;
private final Lifecycle mLifecycle;
private final SavedStateRegistry mSavedStateRegistry;
.......
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
Constructor<T> constructor;
if (isAndroidViewModel) {
constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
} else {
// 如果要创建的 ViewModel 不是 AndroidViewModel
// 的话默认情况下会走这里,而如果所要创建的 ViewModel 中没有
// 只有一个 SavedStateHandle 参数的构造函数的话,这里的
// constructor 会为 null
constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
}
// doesn't need SavedStateHandle
if (constructor == null) {
// 这里调用的是 AndroidViewModelFactory 的 create 方法
return mFactory.create(modelClass);
}
// 走到这里说明我们要创建的 ViewModel 中有一个带有 SavedStateHandle // 的构造函数,上面的 constructor 不为空。这里的 controller
// 实际上是一个保存 SaveState 信息及页面事件监听的控制器,用于保存页面数据及根据生命周期事件来对自己进行管理
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
try {
T viewmodel;
if (isAndroidViewModel) {
viewmodel = constructor.newInstance(mApplication, controller.getHandle());
} else {
viewmodel = constructor.newInstance(controller.getHandle());
}
viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
return viewmodel;
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to access " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("An exception happened in constructor of "
+ modelClass, e.getCause());
}
}
......
}
SavedStateViewModelFactory 是 ViewModel 组件提供的一个默认的创建 ViewModel 的实现类。当我们通过 ViewModelProvider 获取 ViewModel 时,如果没有指定具体用哪个一工厂去创建的话,系统默认使用当前传入的 ViewModelStoreOwner(主要是 Activity 和 Fragment) 中所实现了 getDefaultViewModelProviderFactory()
方法去获取默认的 Factory,而这个默认的 Factory 就是 SavedStateViewModelFactory。
SavedStateViewModelFactory 的作用主要是用于创建可以保存当前页面的数据的 ViewModel。也就是说当我们的页面数据还是保存在页面的时候,我们可以通过该默认工厂,把页面的数据存储到由 SavedStateViewModelFactory 所创建的 ViewModel 中进行保存。前提是:需要我们的 ViewModel 的最终实现类的构造方法签名和下面的 ANDROID_VIEWMODEL_SIGNATURE 或 VIEWMODEL_SIGNATURE 一样才会起到上面说的效果,否则,就会调用 AndroidViewModelFactory 中的 create()
方法去创建 ViewModel 对象。
class SaveStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
}
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
SavedStateHandle.class};
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};
当然这里还有一点需要注意的是:你必须在 Activity 或 Fragment 的对应回调函数 onSaveInstanceState()
中对数据进行了存储才有意义的。
3. SavedStateHandleController
final class SavedStateHandleController implements LifecycleEventObserver {
private final String mKey;
private boolean mIsAttached = false;
private final SavedStateHandle mHandle;
....
void attachToLifecycle(SavedStateRegistry registry, Lifecycle lifecycle) {
if (mIsAttached) {
throw new IllegalStateException("Already attached to lifecycleOwner");
}
mIsAttached = true;
lifecycle.addObserver(this);
registry.registerSavedStateProvider(mKey, mHandle.savedStateProvider());
}
static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
String key, Bundle defaultArgs) {
Bundle restoredState = registry.consumeRestoredStateForKey(key);
SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
SavedStateHandleController controller = new SavedStateHandleController(key, handle);
controller.attachToLifecycle(registry, lifecycle);
tryToAddRecreator(registry, lifecycle);
return controller;
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mIsAttached = false;
source.getLifecycle().removeObserver(this);
}
}
.......
}
SavedStateHandleController 它继承自 LifecycleEventObserver 接口,所以它是一个生命周期事件的观察者,观察生命周期的主要目的是当宿主(Activity 或 Fragment)调用了 ON_DESTROYED 事件后,对自己进行一个移除,以便进行回收。
SavedStateHandleController 除了上面的特点外,还有下面这个主要的职责:它是一个对 SavedStateHandle 对象进行控制的管理类。所有对 SavedStateHandle 的访问都需要通过 SavedStateHandleController 来进行。
它这里主要做了这几件事情:
- 通过 SavedStateViewModelFactory 中的 mLifecycle 对象,将自己绑定在当前页面的生命周期中。并将 SavedStateHandle 中的回调方法
mSavedStateProvider
注册到了 SavedStateRegistry 对象中(这个对象是做什么的,后面会讲),这样做的结果是当页面回调 onSaveInstanceState 时,SavedStateRegistry 在保存数据的过程中,会回调 SavedStateHandle 的saveState()
方法来将 SavedStateHandle 中的数据进行保存。这个主要是在attachToLifecycle()
方法中做的事情。 - 创建 SavedStateHandle 对象和自己。SavedStateHandleController 的创建是通过 SavedStateViewModelFactory 来创建的,由于这里每调用一次
create()
函数就会创建一个新的 SavedStateHandleController 和 SavedStateHandle 对象。也就是说每一个 SavedStateViewModelFactory 对象会对应多个 SavedStateHandleController 及 SavedStateHandle 对象。为什么有这种对应关系呢?这里我觉得主要是因为 SavedStateHandleController 是一个 LifecycleEventObserver 对象,当页面被销毁后,该对象也会被回收,对应的 SavedStateHandle 也会被回收。如果是 configuration 发生变化导致的页面重启的话,新的 SavedStateHandle 对象会将页面的保存数据重新存储到新的 SavedStateHandle 中。 当然,这里其实不会出现创建多个 SavedStateHandleController 和 SavedStateHandle 的情况,主要是因为在get()
方法去获取对应的 ViewModel 时,如果还没有创建才会去创建对应的对象,如果已经创建过了会直接去 mViewModelStore 缓存中获取。
4. SavedStateHandle
public final class SavedStateHandle {
final Map<String, Object> mRegular;
private final Map<String, SavingStateLiveData<?>> mLiveDatas = new HashMap<>();
private static final String VALUES = "values";
private static final String KEYS = "keys";
// 该回调用于当页面被非正常销毁后,保存当前数据使用的
private final SavedStateProvider mSavedStateProvider = new SavedStateProvider() {
@SuppressWarnings("unchecked")
@NonNull
@Override
public Bundle saveState() {
Set<String> keySet = mRegular.keySet();
ArrayList keys = new ArrayList(keySet.size());
ArrayList value = new ArrayList(keys.size());
for (String key : keySet) {
keys.add(key);
value.add(mRegular.get(key));
}
Bundle res = new Bundle();
// "parcelable" arraylists - lol
res.putParcelableArrayList("keys", keys);
res.putParcelableArrayList("values", value);
return res;
}
};
static SavedStateHandle createHandle(@Nullable Bundle restoredState,
@Nullable Bundle defaultState) {
if (restoredState == null && defaultState == null) {
return new SavedStateHandle();
}
// 保存默认状态
Map<String, Object> state = new HashMap<>();
if (defaultState != null) {
for (String key : defaultState.keySet()) {
state.put(key, defaultState.get(key));
}
}
if (restoredState == null) {
return new SavedStateHandle(state);
}
// 如果页面非正常销毁后,保存了数据的话,会在再次创建该对象的时候,
// 将之前页面所保存的数据存储到当前 SavedStateHandle 对象中
ArrayList keys = restoredState.getParcelableArrayList(KEYS);
ArrayList values = restoredState.getParcelableArrayList(VALUES);
if (keys == null || values == null || keys.size() != values.size()) {
throw new IllegalStateException("Invalid bundle passed as restored state");
}
for (int i = 0; i < keys.size(); i++) {
state.put((String) keys.get(i), values.get(i));
}
return new SavedStateHandle(state);
........
}
SavedStateHandle 该对象用于在 ViewModel 对象中保存页面数据。前面我们讲过,只有通过指定 SavedStateViewModelFactory 为创建 ViewModel 的工厂对象,且所实现的 ViewModel 中有一个构造函数是接收一个参数且参数类型为 SavedStateHandle 的情况下,才会使创建出来的 ViewModel 可以接收,保存及使用页面数据。
当页面被非正常销毁后,SavedStateHandle 中的 mSavedStateProvider 监听方法会被调用,以保存当前 SavedStateHandle 中的所有数据,因为 SavedStateHandle 会随着页面的销毁而被回收。
SavedStateHandle 提供了 set(@NonNull String key, @Nullable T value)
方法来保存数据。默认都会以值为 Object 类型的方式存储在 mRegular 参数中。
同时,还提供了两种访问数据的方式,都是通过 key 的方式来获取值,不同的是,返回值的类型有所区别。一种是直接返回类型为 Object 类型的对象。另一种是返回 MutableLiveData 对象。分别对应的存储对象是 Map<String, Object> mRegular
和 Map<String, SavingStateLiveData<?>> mLiveDatas
。这里 mRegular 和 mLiveDatas 的关系又是怎样的呢?
private <T> MutableLiveData<T> getLiveDataInternal(
@NonNull String key,
boolean hasInitialValue,
@Nullable T initialValue) {
MutableLiveData<T> liveData = (MutableLiveData<T>) mLiveDatas.get(key);
// 第一次进来这里的 liveData 为 null
if (liveData != null) {
return liveData;
}
SavingStateLiveData<T> mutableLd;
// double hashing but null is valid value
if (mRegular.containsKey(key)) {
mutableLd = new SavingStateLiveData<>(this, key, (T) mRegular.get(key));
} else if (hasInitialValue) {
mutableLd = new SavingStateLiveData<>(this, key, initialValue);
} else {
// 如果 mRegular 中没有对应的值,而又没有指令默认值,则直接创建一个 // 值为 null 的 LiveData 对象
mutableLd = new SavingStateLiveData<>(this, key);
}
// 将创建的 LiveData 对象缓存起来
mLiveDatas.put(key, mutableLd);
return mutableLd;
}
从其 get()
方法中可以看出,mRegular 是一个全量的数据表,所有的数据都会存储到这里。当调用获取 LiveData 数据类型的数据时,会先去 mLiveDatas 里直接获取,如果不存在,就会去 mRegular 中获取,获取到了,将其封装成 LiveData 对象,并将其缓存到 mLiveDatas 中,然后再返回这个 LiveData 对象。
可以看出,这个 SavedStateHandle 对象是 ViewModel 组件提供的一种默认保存及获取页面数据的方式。
5. SavedStateRegistryController 和 SavedStateRegistry
SavedStateRegistryController 其实什么也没有做,只是一个对 SavedStateRegistry 的代理类。实际的实现都是在 SavedStateRegistry 中做的,所以,我们直接分析 SavedStateRegistry 对象就可以了。
SavedStateRegistry 这个对象是页面真正保存数据的地方,每个页面都绑定了一个 SavedStateRegistry 对象,当页面由于非正常销毁而调用了 ON_DESTROYED 事件后,会调用 onSaveInstanceState(bundle)
回调方法来进行页面的数据保存工作,而真实的数据是保存在 bundle 对象中的。在 onSaveInstanceState(bundle)
回调方法中,调用了 mSavedStateRegistryController.performSave(outState)
方法,实际上就是调用了 SavedStateRegistry 的 performSave(outState)
。
@MainThread
void performSave(@NonNull Bundle outBundle) {
Bundle components = new Bundle();
if (mRestoredState != null) {
components.putAll(mRestoredState);
}
// 通知各个 SavedStateProvider 来保存各自的数据
for (Iterator<Map.Entry<String, SavedStateProvider>> it =
mComponents.iteratorWithAdditions(); it.hasNext(); ) {
Map.Entry<String, SavedStateProvider> entry1 = it.next();
components.putBundle(entry1.getKey(), entry1.getValue().saveState());
}
outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}
这个方法的主要功能主要是将所有通过 registerSavedStateProvider(key,provider)
注册到了 SavedStateRegistry 对象里,表示当页面由非正常销毁时,这些实际了 provider 监听的对象需要进行数据保存。而这里就是回调各个设置了监听对象,通过调用 saveState()
回调函数,来将对象的数据保存到 Bundle 对象中。
而当页面的由于非正常销毁之后重启时,调用了 onCreate(@Nullable Bundle savedInstanceState)
回调函数后,会调用 mSavedStateRegistryController.performRestore(savedInstanceState)
方法对之前保存的数据进行恢复。
void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
if (mRestored) {
throw new IllegalStateException("SavedStateRegistry was already restored.");
}
if (savedState != null) {
mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
}
......
mRestored = true;
}
该方法主要是将保存在 Bundle 对象中的对应的 Bundle 对象获取出来,保存在当前对象的 mRestoredState 中。那对应的订阅了当前对象的 SavedStateProvider 如果恢复其数据呢?
这里的 SavedStateProvider 主要是 SavedStateHandle 对象中实现了 SavedStateProvider 回调函数。我们前面说过,每个 SavedStateViewModelFactory 正常情况下绑定一个 SavedStateHandleController 对象。当页面重新加载的时候,会调用 SavedStateViewModelFactory 去创建对应的 带有一个参数为 SavedStateHandle 的 ViewModel 对象。在创建的过程中,会调用 SavedStateHandleController 的 `create()方法,在这个
create()方法中会调用 SavedStateRegistry 对象的
consumeRestoredStateForKey(key)`` 方法。
public Bundle consumeRestoredStateForKey(@NonNull String key) {
if (!mRestored) {
throw new IllegalStateException("You can consumeRestoredStateForKey "
+ "only after super.onCreate of corresponding component");
}
if (mRestoredState != null) {
Bundle result = mRestoredState.getBundle(key);
mRestoredState.remove(key);
if (mRestoredState.isEmpty()) {
mRestoredState = null;
}
return result;
}
return null;
}
原来其实订阅了监听的对象都是通过该方法去获取之前保存的数据,然后在 SavedStateHandle 重新创建的过程中,通过之前保存时用的 key 作为参数去调用 consumeRestoredStateForKey(key)
函数来获取保存数据并恢复数据的。
static SavedStateHandle createHandle(@Nullable Bundle restoredState,
@Nullable Bundle defaultState) {
if (restoredState == null && defaultState == null) {
return new SavedStateHandle();
}
Map<String, Object> state = new HashMap<>();
if (defaultState != null) {
for (String key : defaultState.keySet()) {
state.put(key, defaultState.get(key));
}
}
if (restoredState == null) {
return new SavedStateHandle(state);
}
ArrayList keys = restoredState.getParcelableArrayList(KEYS);
ArrayList values = restoredState.getParcelableArrayList(VALUES);
if (keys == null || values == null || keys.size() != values.size()) {
throw new IllegalStateException("Invalid bundle passed as restored state");
}
for (int i = 0; i < keys.size(); i++) {
state.put((String) keys.get(i), values.get(i));
}
return new SavedStateHandle(state);
}
上面的方法就是 SavedStateHandle 对象中用来创建对象及恢复数据的方法。
6. AndroidViewModel
AndroidViewModel 也是组件提供的一个默认的实现类,但其实里面什么都没有,只是持有了一个 mApplication 对象。这个 ViewModel 也是由默认的 SavedStateViewModelFactory 工厂对象来创建的,其实功能和上面的实现是差不多的,只是比上面我们的 ViewModel 实现是直接实现 ViewModel 对象的。
class SaveStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
}
而如果想要使用 AndroidViewModel 的话,需要上面的方法直接继承 AndroidViewModel 对象,而不是 ViewModel。
class SaveStateAndroidViewModel(
application: Application,
val savedStateHandle: SavedStateHandle
) : AndroidViewModel(application)
其实就比上面多了一个 application,其它都一样,就不多说了。
讲到这里,我想先对 SaveStateXXXX 相关的交互做一个总结吧。这里一系列的实现,其实就是将 ViewModel 中的数据保存到由页面回调 onSaveInstanceState(@NonNull Bundle outState)
和 onCreate(@Nullable Bundle savedInstanceState)
中的 Bundle 对象中的一系列交互。只要是通过 SavedStateRegistry 对象中的 registerSavedStateProvider()
方法注册了 SavedStateProvider 回调的对象都可以将数据存储到系统提供的 bundle 对象中(关于这里的数据存储的系统实现,在这里不作展开)。
而这里主要是通过一系列的交互实现了一个带 SavedStateHandle 参数的 ViewModel 对象。来将页面和 ViewModel 中的数据都存储在 bundle 对象中。
上图是一个 SaveStateViewModel 的创建过程。图上面已经比较清楚了,我这里就不在赘述了。下面是 SavedStateHandle 保存数据的过程。
下面是 SavedStateHandle 恢复数据的过程。
最后一点就是 SaveStateViewModel 构造函数中 SavedStateHandle 对象哪里来的呢?这个对象其实是 SavedStateViewModelFactory 在创建 SaveStateViewModel 对象之前通过 SavedStateHandleController 对象创建建了 SavedStateHandle 对象并传递给了对应的 SavedStateViewModel 对象。
好了,到这里 ViewModel 在使用页面非正常销毁来保存数据机制的内容就说完了。
7. ViewModel
ViewModel 是组件提供的一个抽象的 ViewModel 类,为方便使用者实现自定义的 ViewModel 对象。
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
// It is possible that we'll call close() multiple times on the same object, but
// Closeable interface requires close method to be idempotent:
// "if the stream is already closed then invoking this method has no effect." (c)
closeWithRuntimeException(result);
}
return result;
}
这个方法主要是用来保存与当前 ViewModel 所绑定的 SavedStateHandle 对象的代理类 SavedStateHandleController。在 SavedStateViewModelFactory 的 create()
方法创建 ViewModel 的时候,调用的该方法,将对应的 SavedStateHandleController 对象缓存了起来。
通过上面的方法可以看出,当 key 对应的对象已经有值的时候,再次调用该方法不会再更新缓存的旧的值。同时将旧的值返回。
<T> T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
该方法就是通过 key 获取对应缓存的 SavedStateHandleController 的值。
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
该方法用于清除所有的缓存数据,并通过 onCleared()
方法回调给 ViewModel 的实现类。而这个方法在什么时候被调用呢?它是在 ViewModelStore 中的 clear
方法被调用的。那这个 ViewModelStore 的 clear()
方法又是在哪里被调用的呢?
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();
}
}
}
});
原来是在对应页面执行了 ON_DESTROY 事件而且在不是屏幕旋转发生配置改变的情况下,就会调用 clear()
方法,用于清除所有缓存在当前页面中的 ViewModel 及 ViewModel 所缓存的 SaveStateHandle
对象。
这也就是为什么数据缓存在 ViewModel 时,当页面由于旋转屏幕而导致的非正常销毁情况下,数据还存在的原因。那新创建的 activity 又是如果再次绑定对应的 ViewModel 对象的呢?
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
其实 Activity 会将创建的对应的 viewModelStore 使用 NonConfigurationInstances 对象缓存起来,当 Activity 在执行 ON_DESTROYED 事件时,当不是因为旋转屏幕导致的销毁的话,就会清除该对应中的缓存;相反,如果是由于旋转屏幕而导致页面重启的话,就不会清除该缓存。当 Activity 重新创建后,通过 getViewModelStore()
去获取 ViewModelStore 对象时,就会直接返回 NonConfigurationInstances 中的缓存,也就完成了将销毁的 Activity 的 ViewModelStore 绑定到了新创建的 Activity 中。
8. ViewModelStore
ViewModelStore 对象就是用于缓存对应页面所创建的 ViewModel 对象。其生命周期与页面(Activity,Fragment)的生命周期是一样的。
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
当通过工厂创建了 ViewModel 对象之后,会调用该 put()
方法将其缓存起来。从上面的代码可以看出,如果 key 相同的情况下,新的 ViewModel 对象会更新旧的值,并回调旧值的 onCleared()
方法来通知旧的对象,已经被回收了。
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
这个 clear()
方法会在页面正常关闭后被调用,用来清除所有 ViewModel 对应的 SavedStateHandle 缓存和 ViewModelStore 中的 ViewModel 缓存。
到这里,所有和 ViewModel 相关的主要类的介绍就已经讲完了。后面我们来分析一下比较重要的流程。
3. 重要流程梳理
3.1. 使用自定义 Factory 创建 ViewModel 的执行流程
class ViewModelFactory(private val repository:MuseumDataSource):ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MuseumViewModel(repository) as T
}
}
ViewModelProvider(this,Injection.provideViewModelFactory()).get(MuseumViewModel::class.java)
上面是一个自定义 Factory 的实现。我们来分析一下通过自定义工厂来获取 ViewModel 的整个执行流程。
- 通过 ViewModelProvider 对象,传入当前 Activity 对象主要是获取其对应的 ViewModelStore 对象,另外一个参数,就是我们创建的自定义的 Factory 工厂类,缓存在 ViewModelProvider 对象中。
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
- 调用
get(clz)
方法获取对应的 ViewModel 对象。这里由于没有指定对应的 key,所以会使用DEFAULT_KEY + cannonicalName
的形式来作为对应的 key 值。
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);
}
接下来调用到了真正的获取 ViewModel 的方法。
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) {
// 这里的最终操作实际上是检测了一下当前 SavedStateViewModel 所绑定的 SavedStateHandle 对象是否未绑定当前页面的生命周期。如果没有,就重新绑定一下。
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
// 如果使用的是系统提供的默认的 SavedStateViewModelFactory 工厂的话,会调用到这里
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
// 自定义工厂所执行的地方
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
上面的方法的主要执行过程是:先从缓存中获取对应的 ViewModel 对象,如果已经有缓存,就直接返回。而如果没有缓存就会调用下面的对应工厂来创建。而我们指定的是对应的自定义工厂,所以会直接执行 (mFactory).create(modelClass)
方法,最后将创建的 ViewModel 缓存起来。
viewModel = (mFactory).create(modelClass);
这里的 mfactory 就是我们上面传递的自定义工厂对象,所以这里直接会调用 override fun <T : ViewModel?> create(modelClass: Class<T>): T
方法。然后,直接创建了自定义的 MuseumViewModel(repository) as T
对象,并将其返回。
到这里,使用自定义 Factory 创建 ViewModel 的执行流程就说完了。整个流程比较的简单,没有太多的东西可以讲的。
3.2. 使用系统默认的工厂来创建 ViewModel 对象
这里分两种情况,一种情况是 ViewModel 中没有一个构造函数的签名满足下面这两种情况:
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
SavedStateHandle.class};
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};
这样的情况下,创建该 ViewModel 的工厂使用的是 NewInstanceFactory 对象,而这个对象的 create()
方法非常的简单。
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
直接通过反射的方式调用了 ViewModel 中无参数的构造函数来创建对应的 ViewModel 对象。这里需要注意的是,如果对应的 ViewModel 中没有无参的构造函数,就会rbqa报错。
另一种情况是 ViewModel 中满足上面两种方法签名中的任意一个。这种情况下,就会使用 SavedStateViewModelFactory 工厂来创建 ViewModel 对象。创建出来的 ViewModel 对象除了具有正常 ViewModel 对象所具备的功能外,还可以通过 SavedStateHandle 对象来保存当前 ViewModel 中的数据。这里主要使用的是页面回调函数 onSaveInstanceState(@NonNull Bundle outState)
中的 outState 的这个 bundle 对象来保存数据。
这个方法被调用的情况主要有以下几种情况:
1、当用户按下HOME键时。
2、从最近应用中选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从当前activity启动一个新的activity时。
5、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。
在前4种情况下,当前activity的生命周期为:
onPause -> onSaveInstanceState -> onStop。
这几种情况都是当出现这些情况后,如果出现 Activity 被系统回收的情况,当再次创建该 Activity 对象时,就会在 onCreate(@Nullable Bundle savedInstanceState)
回调函数中收到之前保存到 outBundle 对象中的 bundle 对象。然后最终通过 SavedStateHandle 进行数据的恢复工作。
第二种方法的好处
默认情况下,ViewModel 只有在 Activity 是因为旋转屏幕的情况被系统重启的情况下,才可以复用之前的 ViewModel,从而达到数据保存的效果。而如果是我们上面所说的其它四种情况导致的 Activity 被销毁后又被重新创建的情况,对应的 ViewModel 也就会回收,这样保存在 ViewModel 的数据也会被回收。而使用第二种情况,当出现上面几种情况的时候,对应的数据还是可以得到缓存,并在重建的时候进行数据恢复。
当然,这里需要使第二种方法起到保存 ViewModel 中数据的作用,还必须自己手动的将 ViewModel 中的数据对象存储到对应的 SavedStateHandle 对象中。
4. 结语
ViewModel 的主流程的分析相对是比较容易的。让我一开始觉得有点困惑的是 SavedStateViewModel 的实现,这一块的知识点,一开始想来到为什么要这么设计,所以,看着比较的不知所措。但当你多看几遍,并通过写具体的代码来实现的时候,甚至通过对代码调试之后,你就会明白设计者设计的用心。在分析源码的时候,不要想着一上来就把所有的东西搞懂。而是需要保持耐心,一遍一遍的多看,多试,多画。只有这样,才有可能弄懂作者设计的用心。