官方介绍
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。
如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。
总结:
- ViewModel 有生命周期感知
- ViewModel 可以在屏幕旋转等情况发生页面销毁重建的情况下,保存数据、恢复数据,虽然 onSaveInstanceState 也可以做到保存数据,但是只能是简单数据且数据量有限制,方式是序列化和反序列化。ViewModel 比 onSaveInstanceState 使用简单方便、数据可操作性强。
- 还能简单的实现 Avtivity 和 Fragment、多个 Fragment 之间的数据共享
使用
- 创建 ViewModel
- ViewModel 中获取数据
- 将数据返回给视图,通常使用 LiveData 简单方便
ViewModel
public abstract class ViewModel {
...
/**
* This method will be called when this ViewModel is no longer used and will be destroyed.
* <p>
* It is useful when ViewModel observes some data and you need to clear this subscription to
* prevent a leak of this ViewModel.
*/
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
.....
}
ViewModel 是个抽象类,主要是有 onCleared 方法,子类可重写此方法,释放资源,此方法会在 ViewModel destroy 时被调用 ViewModel 不能持有视图或 LifecycleOwner 的引用(ViewModel存在的时间更长),如果 ViewModel 中需要持有 Context 上下文,可以使用 AndroidViewModel ,它是 ViewModel 的一个实现了,扩展持有 Application
使用中可以自定义基类 BaseViewModel ,如网络请求或数据库查询等耗时操作时统一Loading处理
open class BaseViewModel : ViewModel() {
//暂时无用,现在每个请求通过ResultState.onAppLoading可实现发起请求的提示作用
val loadingChange: UiLoadingChange by lazy { UiLoadingChange() }
inner class UiLoadingChange {
//显示加载框
val showDialog by lazy { EventLiveData<String>() }
//隐藏
val dismissDialog by lazy { EventLiveData<Boolean>() }
}
}
private fun registerUiChange() {
//显示弹窗
mViewModel.loadingChange.showDialog.observeInActivity(this, Observer {
showLoading(it)
})
//关闭弹窗
mViewModel.loadingChange.dismissDialog.observeInActivity(this, Observer {
dismissLoading()
})
}
创建ViewModel
val testViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
ViewModel 要保存数据(数据共享)不能使用 ViewModel 直接创建对象的方式,通过 ViewModelProvider 提供 ViewModel 实例,已达到此目的。
ViewModelProvider 有是三个构造函数
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
最终都是给 ViewModelStore 和 Factory 赋值
ComponentActivity(FragmentActivity继承自它)和 Fragment 都 实现了 ViewModelStoreOwner 接口,重写 getViewModelStore 以获取 ViewModelStore
ViewModelStore
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 是对 Map 的封装,用于存储不同的 ViewModel
Factory
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
Factory 是工厂接口,用于创建 ViewModel ,Activity 和 Fragment 都实现了 HasDefaultViewModelProviderFactory 接口,提供 SavedStateViewModelFactory(实现了Factory)
@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 && mApplication != null) {
constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
} else {
constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
}
// doesn't need SavedStateHandle
if (constructor == null) {
return mFactory.create(modelClass);
}
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
try {
T viewmodel;
if (isAndroidViewModel && mApplication != null) {
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());
}
}
通过反射的方式创建 ViewModel
回到 ViewModelProvider 的 get 方法
@NonNull
@MainThread
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);
}
@NonNull
@MainThread
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,如果存在则直接返回此 ViewModel,若不存在则通过提供的 Factory 创建ViewModel 并存入ViewModelStore,同时返回。所以只要是同一个 ViewModelStore,去获取特定 ViewModel 时就能获取同一对象,以实现多个视图数据共享。如 不同 Fragment 中调用val fgTestViewModel: MainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java) 就能获取到同一 ViewModel。
数据共享
上面说过实现数据共享是运维 ViewModelStore 相同,Fragment 因为传入相同的 Activity 所以 ViewModelStore 是相同的,但是屏幕旋转导致的页面重建, Activity 肯定是不相同的为什么说还能通过 ViewModel 来回复数据呢。
那就先看看销毁时做了什么操作,以 Activity 为例
//FragmentActivity
@Override
protected void onDestroy() {
super.onDestroy();
mFragments.dispatchDestroy();
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
//ComponentActivity
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
//Activity
public boolean isChangingConfigurations() {
return mChangingConfigurations;
}
mChangingConfigurations是否是因新配置(如旋转屏幕导致重建),为true就不会清除ViewModelStore数据,mChangingConfigurations 在 ActivityThread handleRelaunchActivity 方法中被置为 true
接下来看因屏幕旋转导致页面销毁重建,源码中很难找到相关的方法,那就从正常情况的 getViewModelStore 看起
@NonNull
@Override
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.");
}
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
可以看到 ensureViewModelStore 方法中会从 NonConfigurationInstances 中获取 ViewModelStore ,若 ViewModelStore 依旧为空才会重新创建。找到 getLastNonConfigurationInstance 方法
/**
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
* {@link #onStart} calls to the new instance, allowing you to extract
* any useful dynamic state from the previous instance.
* ....
*
* @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
*/
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
大概意思是通过 getLastNonConfigurationInstance 可以获取到之前 onRetainNonConfigurationInstance 方法返回的实例
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@Nullable
@SuppressWarnings("deprecation")
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
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;
}
这个方法将 ViewModelStore 保存在 NonConfigurationInstances 对象中,并将 NonConfigurationInstances 返回。在页面创建时,调用到了 getViewModelStore 方法,进而调用 getLastNonConfigurationInstance 获取 NonConfigurationInstances 对象,从而获取之前的 ViewModelStore 。
PS
onRetainNonConfigurationInstance 返回的 NonConfigurationInstances 对象被谁接收、如何保存、如何在重建时传递 涉及到页面旋转重启的流程,没有深入,从源码上看,onRetainNonConfigurationInstance 在 Activity 中被 retainNonConfigurationInstances 调用 onRetainNonConfigurationInstance 返回的对象被存到 NonConfigurationInstances 对象中并将该对象返回。而 retainNonConfigurationInstances 方法在 ActivityThread performDestroyActivity 方法中被调用,结果被保存在 lastNonConfigurationInstances 变量,最终调用到 performLaunchActivity ,方法中 activity 调用 attach 方法将值传回
2021-06-03 17:41:21.852 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: getLastNonConfigurationInstance
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onCreate
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: store = androidx.lifecycle.ViewModelStore@fbb7964
2021-06-03 17:41:21.948 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: model = com.cc.kotlin_base_mvvm.MainViewModel@4a20394
2021-06-03 17:41:21.966 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStart
2021-06-03 17:41:21.976 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onResume
2021-06-03 17:41:28.682 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onPause
2021-06-03 17:41:28.694 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStop
2021-06-03 17:41:28.718 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onDestroy
2021-06-03 17:41:28.834 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: getLastNonConfigurationInstance
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onCreate
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: store = androidx.lifecycle.ViewModelStore@fbb7964
2021-06-03 17:41:28.991 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: model = com.cc.kotlin_base_mvvm.MainViewModel@4a20394
2021-06-03 17:41:29.012 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onStart
2021-06-03 17:41:29.023 25397-25397/com.cc.kotlin_base_mvvm E/ccc0603: onResume
onRetainNonConfigurationInstance 在onStop 之后 onDestory 之前调用(无法重写,可以断点查看),getLastNonConfigurationInstance 在 onCreate 之前就被调用,屏幕旋转后可以看到 ViewModelStore 和 ViewModel 和页面重建之前是同一个对象
Fragment 中应该是和 Activity 中类似的,但是 Fragment 依附于 Activity,且 Fragment 中是使用 FragmentManager 来实现 getViewModelStore 操作的,与 Activity 不同,但内部流程原理大致应该是类似的。
与Kotlin协程使用
来看个官方文档的例子
ViewModelScope:
为应用中的每个 ViewModel 定义了 ViewModelScope。如果 ViewModel已清除,则在此范围内启动的协程都会自动取消。如果您具有仅在 ViewModel 处于活动状态时才需要完成的工作,此时协程非常有用。例如,如果要为布局计算某些数据,则应将工作范围限定至 ViewModel,以便在ViewModel清除后,系统会自动取消工作以避免消耗资源。您可以通过 ViewModel 的 viewModelScope 属性访问 ViewModel 的 CoroutineScope,如以下示例所示:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
也就是说,用 ViewModelScope.launch 启动的协程会在 ViewModel 被销毁时自动取消,即协程中正在进行网络请求,此时页面被销毁,会回调 ViewModel 的 onCleared 方法,我们无需在这个方法中做有关协程的取消,并不会有回调请求结果、通知页面更新等操作,避免内存泄露