认识 ViewModel
ViewModel 是一种用来存储和管理UI相关数据的类。
ViewModel 的作用可以从两个方面去理解:
- UI界面控制器:在最初的MVC模式中,由于 Activity / Fragment 承担的职责过重,因此在后续的 MVP、MVVM 模式中,选择将 Activity / Fragment 中与视图无关的职责抽离出来,在 MVP 模式中叫作 Presenter,在 MVVM 模式中叫作 ViewModel。使用 ViewModel 来承担界面控制器的职责,并且配合 LiveData / Flow 实现数据驱动。
- 数据存储:由于 Activity 存在因配置变更销毁重建的机制,会造成 Activity 中的所有瞬态数据丢失,例如网络请求得到的用户信息、视频播放信息或者异步任务都会丢失。而 ViewModel 的特点是生命周期长于 Activity ,因此能够应对 Activity 因配置变更而重建的场景,在重建的过程中恢复 ViewModel 数据,从而降低用户体验受损。
ViewModel 生命周期示意图:
使用 ViewModel
使用步骤:
- 自定义 ViewModel 继承 ViewModel。
- 在自定义 ViewModel 中编写获取UI数据的逻辑。
- 配合使用 LiveData / Flow 实现数据驱动。
- 在 Activity / Fragment中 获取 ViewModel 实例。
- 监听或收集 ViewModel 中的 LiveData / Flow 数据,进行对应的UI更新。
简单示例:
class TestFlowViewModel : ViewModel() {private val _state: MutableStateFlow<Int> = MutableStateFlow(0)val state: StateFlow<Int> get() = _stateprivate val _live: MutableLiveData<String> = MutableLiveData<String>()val live: LiveData<String> get() = _livefun test() {_live.value = "1"for (state in 1..5) {viewModelScope.launch(Dispatchers.IO) {delay(100L * state)_state.emit(state)}}}
}
Activity / Fragment 中相关代码:
private val viewModel: TestFlowViewModel by viewModels()lifecycleScope.launch {launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.state.collect {Log.d(TAG, "state: $it ")}}}delay(100)viewModel.test()
}
viewModel.live.observe(this) {Log.d(TAG, "it: $it")
}
在 Activity / Fragment中 获取 ViewModel 实例有两种方式,一种是通过ViewModelProvider获取,也key自定义ViewModelProvider.Factory,
private val viewModel = ViewModelProvider(this).get(TestFlowViewModel::class.java)
另一种就是上面示例里面使用的方式:使用 Kotlin by 委托属性,本质上是间接使用了 ViewModelProvider。
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(noinline extrasProducer: (() -> CreationExtras)? = null,noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {val factoryPromise = factoryProducer ?: {defaultViewModelProviderFactory}return ViewModelLazy(VM::class,{ viewModelStore },factoryPromise,{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras })
}public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(private val viewModelClass: KClass<VM>,private val storeProducer: () -> ViewModelStore,private val factoryProducer: () -> ViewModelProvider.Factory,private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {private var cached: VM? = nulloverride val value: VMget() {val viewModel = cachedreturn if (viewModel == null) {val factory = factoryProducer()val store = storeProducer()ViewModelProvider(store,factory,extrasProducer()).get(viewModelClass.java).also {cached = it}} else {viewModel}}override fun isInitialized(): Boolean = cached != null
}
分析 ViewModel 原理
ViewModel 创建过程
上面说到了创建 ViewModel 实例的方法最终都是通过 ViewModelProvider 完成的。ViewModelProvider 可以理解为创建 ViewModel 的工具类,它需要 2 个参数:
- ViewModelStoreOwner: 它对应于 Activity / Fragment 等持有 ViewModel 的宿主,它们内部通过 ViewModelStore 维持一个 ViewModel 的映射表,ViewModelStore 是实现 ViewModel 作用域和数据恢复的关键;
- Factory: 它对应于 ViewModel 的创建工厂,缺省时将使用默认的 NewInstanceFactory 工厂来反射创建 ViewModel 实例。
创建 ViewModelProvider 工具类后,通过 get() 方法来获取 ViewModel 的实例。get() 方法内部首先会从 ViewModelStore 中取缓存,没有缓存才会通过 ViewModel 工厂创建实例再缓存到 ViewModelStore 中。
@MainThreadpublic open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {// 先从 ViewModelStore 中取缓存val viewModel = store[key]// 存在 ViewModel,直接返回if (modelClass.isInstance(viewModel)) {(factory as? OnRequeryFactory)?.onRequery(viewModel!!)return viewModel as T} else {@Suppress("ControlFlowWithEmptyBody")if (viewModel != null) {// TODO: log a warning.}}val extras = MutableCreationExtras(defaultCreationExtras)extras[VIEW_MODEL_KEY] = key// AGP has some desugaring issues associated with compileOnly dependencies so we need to// fall back to the other create method to keep from crashing.// 不存在则使用 ViewModel 工厂创建实例,并放入 ViewModelStorereturn try {factory.create(modelClass, extras)} catch (e: AbstractMethodError) {factory.create(modelClass)}.also { store.put(key, it) }}
ViewModelStore 是 ViewModel 存储器,内部通过 LinkedHashMap 存储 ViewModel。
ViewModelStoreOwner 是一个接口,ViewModelStore 的持有者是ViewModelStoreOwner 的实现类,包括有ComponentActivity和Fragment,它们内部都保存着一个 ViewModelStore。
interface ViewModelStoreOwner {/*** The owned [ViewModelStore]*/val viewModelStore: ViewModelStore
}
public class ComponentActivity extends androidx.core.app.ComponentActivity implementsContextAware,LifecycleOwner,ViewModelStoreOwner ... {private ViewModelStore mViewModelStore;private ViewModelProvider.Factory mDefaultFactory;@NonNull@Overridepublic ViewModelStore getViewModelStore() {...ensureViewModelStore();return mViewModelStore;}
}
正因为 ViewModel 宿主内部都保存着一个 ViewModelStore ,因此在同一个宿主上重复调用 ViewModelProvider#get() 返回同一个 ViewModel 实例,这也就做到了 fragment 共享 activity 的ViewModel 实例以及 fragment 之间共享 ViewModel。
为什么 Activity 在屏幕旋转重建后可以恢复 ViewModel?
上面说到 ViewModel 其实是被保存在 ViewModelStore 里,所以 Activity 在屏幕旋转重建后恢复 ViewModel 其实是重新获取到了原有的 ViewModelStore。那么 Activity 里的 ViewModelStore 究竟是怎么生成的呢?
ViewModelStore 的生成
ComponentActivity 在构造函数中设置了对 lifecycle 的监听,当 activity 的生命周期变化时会调用ensureViewModelStore方法,确保 ViewModelStore 的存在。
getLifecycle().addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {ensureViewModelStore();getLifecycle().removeObserver(this);}});
@SuppressWarnings("WeakerAccess") /* synthetic access */void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstancesmViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}}
当 ViewModelStore 不存在时,ensureViewModelStore 方法会进行 ViewModelStore 的生成,首先通过 getLastNonConfigurationInstance 获取到 NonConfigurationInstances,并从中获取 ViewModelStore,若 ViewModelStore 仍未空,则创建 new 一个新的。
看到这,知道了 ViewModelStore 的生成来源于两处,一处为 NonConfigurationInstances 中获取,另一处是新创建。那么 Activity 在屏幕旋转重建后获取到了原有的 ViewModelStore 是不是就是从 NonConfigurationInstances 中获取的呢?
接下去看一下 Activity 在屏幕旋转重建后,ViewModelStore 都干什么去了呢?
Activity 因配置变更而重建时(比如屏幕旋转),可以将页面上的数据或状态可以定义为 2 类:
- 配置数据:例如窗口大小、多语言字符、多主题资源等,当设备配置变更时,需要根据最新的配置重新读取新的数据,因此这部分数据在配置变更后便失去意义,自然也就没有存在的价值;
- 非配置数据:例如用户信息、视频播放信息、异步任务等非配置相关数据,这些数据跟设备配置没有一点关系,如果在重建 Activity 的过程中丢失,不仅没有必要,而且会损失用户体验(无法快速恢复页面数据,或者丢失页面进度)。
基于以上考虑,Activity 是支持在设备配置变更重建时恢复非配置数据的,源码中存在NonConfiguration
字眼的代码,就是与这个机制相关的代码。
当 Activity 因配置变更而重建时,ActivityThread
的 handleRelaunchActivity
方法会执行,先 handleDestroyActivity
销毁 Activity,然后 handleLaunchActivity
重建 Activity。
Activity 销毁过程
在 handleDestroyActivity 方法里执行到 performDestroyActivity
时,会执行 activity 的 retainNonConfigurationInstances
方法,将非配置数据临时存储在当前 Activity 的 ActivityClientRecord(当前进程内存)。
void performDestroyActivity(ActivityClientRecord r, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {Class<? extends Activity> activityClass = null;if (localLOGV) Slog.v(TAG, "Performing finish of " + r);activityClass = r.activity.getClass();r.activity.mConfigChangeFlags |= configChanges;if (finishing) {r.activity.mFinished = true;}performPauseActivityIfNeeded(r, "destroy");if (!r.stopped) {callActivityOnStop(r, false /* saveState */, "destroy");}if (getNonConfigInstance) {try {// 将非配置数据临时存储在 ActivityClientRecordr.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();} catch (Exception e) {if (!mInstrumentation.onException(r.activity, e)) {throw new RuntimeException("Unable to retain activity "+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);}}}...}
// 获取 Activity 的非配置相关数据
NonConfigurationInstances retainNonConfigurationInstances() {// 构造 Activity 级别的非配置数据Object activity = onRetainNonConfigurationInstance();...// 构造 Fragment 级别的费配置数据数据FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();...// 构造并返回 NonConfigurationInstances 非配置相关数据类NonConfigurationInstances nci = new NonConfigurationInstances();nci.activity = activity;nci.fragments = fragments;...return nci;
}// 默认返回 null,由 Activity 子类定义
public Object onRetainNonConfigurationInstance() {return null;
}
看一下onRetainNonConfigurationInstance
在 ComponentActivity 中的实现:
public final Object onRetainNonConfigurationInstance() {// Maintain backward compatibility.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// 这一个 if 语句是处理异常边界情况: // 如果重建的 Activity 没有调用 getViewModelStore(),那么旧的 Activity 中的 ViewModel 并没有被取出来, // 因此在准备再一次存储当前 Activity 时,需要检查一下旧 Activity 传过来的数据。NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}// ViewModelStore 为空说明当前 Activity 和旧 Activity 都没有 ViewModel,没必要存储和恢复if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;// 保存 ViewModelStore 对象nci.viewModelStore = viewModelStore;return nci;}
看到了 ViewModelStore 实例的保存,就这样,当 Activity 被销毁时,ViewModelStore 实例被保存进了 NonConfigurationInstances 中,进而被临时存储在了 ActivityClientRecord 里。
Activity 重建过程
在 handleLaunchActivity 方法里执行到 performLaunchActivity
时,会执行 activity 的attach
方法,并将 ActivityClientRecord中的 NonConfigurationInstances 传入。
// 在 Activity#attach() 中传递旧 Activity 的数据
NonConfigurationInstances mLastNonConfigurationInstances;final void attach(Context context, ActivityThread aThread,...NonConfigurationInstances lastNonConfigurationInstances) {...mLastNonConfigurationInstances = lastNonConfigurationInstances;...
}
至此,旧 Activity 的数据就传递到新 Activity 的成员变量 mLastNonConfigurationInstances 中了,ViewModelStore 将从 mLastNonConfigurationInstances 中获取。
ViewModel 数据清除
ViewModel 的数据又是在什么时候会被清除呢?
ViewModel 中有一个 clear 方法用于数据清除,在 ViewModelStore#clear() 方法中被调用。
上面提到 ViewModelStore 的生成是 ComponentActivity 在构造函数中设置了对 lifecycle 的监听,当 activity 的生命周期变化时会调用ensureViewModelStore方法,确保 ViewModelStore 的存在。
ComponentActivity 在构造函数中设置对 lifecycle 的监听可不只有这一处,ViewModel 数据的清除也是通过对 lifecycle 的监听,当 Activity 进入 destroyed 状态,并且 Activity 不处于配置变更重建的阶段,将调用 ViewModelStore#clear() 清除 ViewModel 数据。
getLifecycle().addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (event == Lifecycle.Event.ON_DESTROY) {// Clear out the available contextmContextAwareHelper.clearAvailableContext();// And clear the ViewModelStoreif (!isChangingConfigurations()) {getViewModelStore().clear();}}}});