目录
- 1.前言
- 2.源码分析
- 3.常见问题
- 4.总结
一、前言
在ViewModel出现之前,在Android开发过程中一定绕不开的是处理Activity/Fragment重建后恢复数据,以及处理可能的内存泄漏问题。那么ViewModel就是能让开发者更为简单处理此类场景的能手。
ViewModel作为JetPack中重要的一员,是用于在Activity/Fragment中管理数据的容器。不会因为设置变更而销毁,同时还不会产生内存泄漏问题。
二、源码分析
在讲解原理前首先看看ViewModel是如何使用的。首先定义一个ViewModel
class MyViewModel: ViewModel() {
val downloadStatus = MutableLiveData<String>()
fun updateDownloadStatus() {
//网络请求等耗时操作
downloadStatue.value = "8%"
}
}
复制代码
然后在Activity中使用ViewModel类。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
private val mViewModel by viewModels()
mViewModel.downloadStatus.observe(this, Observer {
//更新UI
updateDownloadButtonStatus(it)
})
}
}
复制代码
通过以上代码,即可实现一个无内存泄漏的数据获取。且在Activity重建时,ViewModel获取到的仍然是一开始创建的对象,因此其保存数据也不会丢失。当Activity被销毁时,会自动调用ViewModel的onCleared方法。清理ViewModel对象。
到这里我们会产生几个疑问
1.ViewModel是如何创建的,为什么不能直接调用构造方法?
2.ViewModel是如何在Activity重建后保存其状态的?
3.ViewModel为什么无需手动销毁?
下面我们来深入到相关源码中找出答案。首先查看ViewModel是如何被创建的。
viewModels()实际上是ComponentActivity的扩展方法。
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
//来自ComponentActivity 的getDefaultViewModelProviderFactory()
//版本:androidx.activity.activity:1.1.0
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
//来自ComponentActivity 的getViewModelStore()
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
public class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
//创建ViewModel
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
//ViewModelProvider [1]
//ViewModelStore [2]
//ViewModelProvider.Factory[3]
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
复制代码
从代码中可以看到在创建ViewModel的过程中涉及到了以下几个类
ViewModel, ViewModelProvider, ViewModelProvider.Factory, ViewModelStore, ViewModelStoreOwner
那么我们来逐个分析各个类的作用:
ViewModelProvider:提供方法给到外部创建ViewModel实例。
ViewModelProvider.Factory:提供创建ViewModel的方式
ViewModelStore:缓存了ViewModel,里边使用了hashMap来存储ViewModel实例
ViewModelStoreOwner: 作为ViewModelStore的拥有者
首先从入口类ViewModelProvider进行分析。
//ViewModeProvider.java
@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) {
//从ViewModelStore缓存中获取。因此ViewModel的状态保存就将职责放到了ViewModelStore
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
//当缓存中没有时,就调用Factory的create方法创建ViewModel对象
viewModel = (mFactory).create(modelClass);
}
//将ViewModel放入ViewModelStore缓存
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
复制代码
从上面代码可以看出如果我们在代码中直接调用ViewModel的构造方法,会发现状态并无法保存(必然的,因为每次重建都会新建一个ViewModel对象)。因此我们在获取ViewModel对象时,应当通过ViewModelProvider类来创建,其处理了ViewModel的创建方式和缓存策略。
通过代码可以看出要保存ViewModel对象,就是要将ViewModelStore对象在Activity/Fragment状态变化时,做好保存和恢复工作即可。那么下面我们回到ComponentActivity中,分析在Activity中是如何保存和恢复的。
//androidx.activity.ComponentActivity
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
....省略
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
//当处于销毁状态且不是由于Configuration改变时,清空ViewModel对象
getViewModelStore().clear();
}
}
}
});
/**
* 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
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
//获取的即是最后一次onRetainNonConfigurationInstance的返回值
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;
}
}
复制代码
这里代码就可以解决问题二和问题三,在ComponentActivity中ViewModelStore的保存时是通过onRetainNonConfigurationInstance机制来实现的。通过将ViewModelStore传入自定义NonConfigurationInstances对象,并在其方法中返回。即可实现ViewModel对象的保存。
然后再通过getLastNonConfigurationInstance方法获取保存的对象。
三、常见问题
1.如何使用ViewModel在两个Fragment之间共享数据?
答:在使用ViewModelProvider创建时,传入Activity即可。因为实际上传入的是ViewModelStoreOwner,其实现了getViewModelStore方法。因此生成的对象是存储在Activity中的。所以在同一Activity下的fragment获取到的就是同一个ViewModel对象。
例:
//内部传的是Activity的ViewModelStore和Factory
private val mViewModel by activityViewModels()
复制代码
2.如果想在Activity之间共享数据呢?
答:本质上我们让不同的Activity之间获取到同一个ViewModel对象即可实现共享。从上面的代码分析及Fragment之间共享数据的实现方式来看,可以通过传入的ViewModelStore来控制其访问范围。因此我们在Application中实现ViewModelStoreOwner接口。并在创建ViewModel时传入Application即可。
又或者通过自定义的ViewModelProvider.Factory来实现,使得不同的Activity获取ViewModel时返回相同的对象即可。
3.ViewModel中为什么不能持有View或者Activity等对象呢?
答:因为Activity/Fragment的生命周期与ViewModel并不相同。如果持有View或者Activity,那么实际上此时Activity可能处于任何的生命周期。此时去操作view的方法就可能出现crash.下面是在屏幕旋转时,Activity和ViewModel生命周期的对照图。
4.ViewModel在什么情况下会保存其状态?
答:在ConfigurationChange的情况下,才会保存状态。如果是由于系统原因被kill掉时,则不会保存。这个因为onRetainNonConfigurationInstance与onSaveInstanceState机制上的区别。如果业务场景需要ViewModel在此种场景下保存状态,那么可以查看SavedStateHandle(viewmodel-savedstate库)相关内容。
四、总结
本篇通过跟踪ViewModel的创建代码,引出了ViewModel创建过程中各个类(ViewModelProvider,
ViewModelProvider.Factory,ViewModelStore,ViewModelStoreOwner)的用途,以及其实现状态保存的实现方式。从实现方式中可以知道ViewModel在页面被系统kill掉时是不会保存其状态的。而如果要通过ViewModel实现则需要额外的使用viewmodel-savedstate库来实现此功能。