简介
ViewModel在架构中用于承载业务逻辑和作为容器保存屏幕状态,它可以缓存界面的状态,并且能在配置变更后持久保留相应的界面状态。
在jetpack套件中,ViewModel随lifecycle一起提供。
优势
- 可以持久的保持界面状态:一是界面因配置变更导致的重建,不会销毁内存中的;二是可以借助SavedStateHandle在进程销毁-重建过程中恢复数据。
- ViewModel具有作用域(如:Activity、Fragment等),ViewModel中的异步工作将被限定在这个Lifecycle上执行。
- ViewModel可用用来承载之前处于界面层的部分业务逻辑:将数据层传递的数据处理成界面状态。
- 可以作为桥梁在Activity与Fragment、Fragment与Fragment之间共享数据。
使用
引入
定义
// 直接继承ViewModel
class DemoViewModel : ViewModel() {
private val api = MyService()
// 通常配合LiveData、StateFlow这些可感知对象为界面提供状态。
private val _uiState = MutableLiveData("")
val uiState: LiveData<String> = _uiState
// 使用SharedFlow为界面提供事件回调
private val _uiEvent = MutableSharedFlow<DemoEvent>()
val uiEvent = _uiEvent.asSharedFlow()
fun reqData(param: String) {
// ViewModel能自动处理协程scope的生命周期
viewModelScope.launch(Dispatchers.IO) {
_uiEvent.emit(DemoEvent.Loading)
try {
val data = api.reqData(param)
_uiState.postValue("rsp: $data")
} finally {
_uiEvent.emit(DemoEvent.Completed)
}
}
}
}
基本用法
在androidx的Activity中使用:
class ViewModelDemoActivity : AppCompatActivity() {
private lateinit var viewModel: DemoViewModel
// viewmodel的ktx扩展库中提供了委托方式获取viewmodel实例
// private val viewModel: DemoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_model_demo)
// ViewModelProvider是获取viewmodel的基础工具,它需要一个ViewModelStoreOwner实例
// 这个实例就是用来存储和管理viewmodel的,androidx的ComponentActivity
// 实现了这个接口,因此可以直接使用AppCompatActivity来初始化ViewModelProvider。
viewModel = ViewModelProvider(this).get(DemoViewModel::class.java)
// 监听界面状态以及事件,并做出响应
viewModel.uiState.observe(this) {
Log.d(TAG, "received response: $it")
}
viewModel.uiEvent
.onEach { showLoading(it == DemoEvent.Loading) }
.launchIn(lifecycleScope)
viewModel.reqData(param)
}
}
在androidx的Fragment中使用:
class DemoFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// jetpack fragment也实现了ViewModelStoreOwner接口,因此也可以用于获取和管理viewmodel
val selfViewModel = ViewModelProvider(this).get(FragmentViewModel::class.java)
}
override fun onAttach(context: Context) {
super.onAttach(context)
// 可以获取到Activity或者其它Fragment的ViewModel,只需要在构造ViewModelProvider
// 时传递了对应的ViewModelStoreOwner。这个实例和DemoActivity
// 中获取到的是同一个实例,因此你可以通过这个实例实现和Activity的通信。
val parentViewModel = ViewModelProvider(requireActivity())
.get(DemoViewModel::class.java)
}
}
SavedStateHandle
SavedStateHandle主要用于在进程销毁-重建过程中恢复数据,它可以将数据持久化到存储中,并在重建后恢复数据。
class DemoViewModel(val savedState: SavedStateHandle) : ViewModel() {
// 可以使用getLiveData将要获取的数据转为LiveData
private val _savedData = savedState.getLiveData<String>(DATA_KEY)
val savedData: LiveData<String> = _savedData
fun saveData(data: String) {
savedState[DATA_KEY] = data
}
fun readData(): String? {
// 也可以直接获取
return savedState[DATA_KEY]
}
companion object {
private const val DATA_KEY = "data"
}
}
AndroidViewModel
有时候ViewModel中可能会需要使用到Android Context(获取文本、颜色等资源),此时可以使用AndroidViewModel,它提供了一个getApplication()方法,可以很方便的获取上下文实例。使用方式如下:
class DemoViewModel(application: Application)
: AndroidViewModel(application) {
fun getString() = application.getString(R.string.hint_txt)
}
带参数的ViewModel
前面几个小节我们都假定了使用androidx的组件作为ViewModelStoreOwner
来构造ViewModelProvider。
这些androidx的组件会帮助我们自动提供ViewModel所依赖的SavedStateHandle
和Application
。
然而,当我们使用自定义ViewModelStoreOwner
时,或者想向ViewModel传递其它类型的参数时,就需要自定义ViewModeProvider.Factory
了。
假如我们有如下ViewModel,它需要接收一个Repository作为参数:
class MyViewModel(
private val myRepository: MyRepository
) : ViewModel() { }
为了实例化MyViewModel,我们需要再定义一个Factory,然后在create
方法中获取依赖对象,构造ViewModel实例:
val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val repo = MyRepository(extras[MY_URL])
return MyViewModel(repo) as T
}
}
上面的CreationExtras
用于从外界向ViewModel构造过程传递参数,它在ViewModelProvider
构造时与factory实例一起传递给ViewModelProvider:
val extras = MutableCreationExtras().apply {
this[MY_URL] = "https://..."
}
val viewModel = ViewModelProvider(this, factory, extras)
.get(MyViewModel::class.java)
原理分析
ViewModel的获取过程
ViewModelProvider的构造
顾名思义,ViewModelProvider
就是用于提供ViewModel实例的类,它在构造时需要接受三个参数:
public open class ViewModelProvider
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)
-
ViewModelStore
:用于存储ViewModel实例的类,内部持有一个HashMap保存实例,ViewModelProvider
会将创建好的ViewModel实例保存到ViewModelStore
中,之后再需要此类ViewModel的实例时就直接从中读取。 -
ViewModelProvider.Factory
:前文已经提到,这是用于创建ViewModel实例的工厂,ViewModelProvider
当需要ViewModel的实例又在ViewModelStore
中没有找到对应实例时就会调用工厂的create
方法创建。 -
CreationExtras
:前文也已提到,它用于在创建ViewModel实例时从外界向构造过程传递参数,内部持有一个MutableMap,以key-value的形式存储和查找参数。
虽然ViewModelProvider
需要三个参数来构造,但在实际使用中我们往往只在构造时传递了一个ViewModelStoreOwner
,ViewModelStoreOwner
很好理解,可以用来提供ViewModelStore
,而剩下两个参数,框架则提供了一系列的默认规则。
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
ViewModelStore的获取
通常情况下ViewModelStore
由ViewModelStoreOwner
提供,ViewModelStoreOwner
是一个接口,里面只声明了一个getViewModelStore
函数。androidx里的ComponentActivity
、Fragment
、FragmentViewLifecycleOwner
等都实现了这个接口,下面我们看一看ComponentActivity
中是如何实现的:
public ViewModelStore getViewModelStore() {
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
// 这里会尝获取配置变更前保存的实例,这是ViewModel在配置变更后仍能保持数据的关键
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
默认工厂和及构造参数
defaultFactory
, defaultCreationExtras
用于提供默认的ViewModelProvider.Factory
与CreationExtras
。
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory
else instance
internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras =
if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelCreationExtras
else CreationExtras.Empty
可以看到,两个方法首先都尝试将ViewModelStoreOwner
实例转为HasDefaultViewModelProviderFactory
,然后从中获取对应的默认值。如果没获取到,则返回ViewModelProvider
自己提供的默认值。
先来看下ViewModelProvider
提供的默认值:
public open class NewInstanceFactory : Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return try {
modelClass.newInstance()
} catch (...) {
...
}
}
public companion object {
private var sInstance: NewInstanceFactory? = null
public val instance: NewInstanceFactory get() {
if (sInstance == null) sInstance = NewInstanceFactory()
return sInstance!!
}
}
}
可以看到,这个工厂通过直接调用Class的newInstance
方法直接创建实例,这种情况下ViewModel必需要提供无参构造函数。
接下来我们看HasDefaultViewModelProviderFactory
,这也是一个接口,里面声明了getDefaultViewModelProviderFactory
与getDefaultViewModelCreationExtras
两个方法,分别用于获取默认的工厂实例与默认的构造参数。androidx中的ComponentActivity
与Fragment
也实现了这个接口,以ComponentActivity
中的实现为例:
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
public CreationExtras getDefaultViewModelCreationExtras() {
MutableCreationExtras extras = new MutableCreationExtras();
if (getApplication() != null) {
extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, getApplication());
}
extras.set(SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY, this);
extras.set(SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY, this);
if (getIntent() != null && getIntent().getExtras() != null) {
extras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, getIntent().getExtras());
}
return extras;
}
ComponentActivity
会提供一个SavedStateViewModelFactory
实例,并且会提供一个预置了一些内容的CreationExtras
实例,里面有Application实例、SavedStateRegistryOwner 的实例、ViewModelStoreOwner的实例,以及Intent中extras参数bundle。
然后是SavedStateViewModelFactory
,我们直接看create
方法:
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
?: throw IllegalStateException("VIEW_MODEL_KEY must always be provided by ViewModelProvider")
return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {
val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
} else {
findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
}
// doesn't need SavedStateHandle
if (constructor == null) {
return factory.create(modelClass, extras)
}
val viewModel = if (isAndroidViewModel && application != null) {
newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
} else {
newInstance(modelClass, constructor, extras.createSavedStateHandle())
}
viewModel
} else {
// 这里是为了兼容旧版本
...
}
}
除开旧版本的兼容逻辑,上面的代码根据是否使用SavedStateHandle分为两类:当不使用SavedStateHandle
时,将ViewModel的构造请求发送给内部的AndroidViewModelFactory
实例来处理;当使用SavedStateHandle
时,则自己调用createSavedStateHandle
方法创建SavedStateHandle
实例,然后创建对应的ViewModel实例。关于SavedStateHandle
的分析见后文。
获取ViewModel
回到开始,我们通过调用ViewModelProvider
实例的get
方法来获取ViewModel实例:
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("${ViewModelProvider.AndroidViewModelFactory.DEFAULT_KEY}:$canonicalName", modelClass)
}
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? ViewModelProvider.OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
if (viewModel != null) { }
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY] = key
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
获取实例需要两个参数:key和要获取的ViewModel所属类的Class对象,ViewModelProvider
会从ViewModelStore
中根据key查找是否有现成的实例,有就直接使用,没有就调用Factory的create
创建一个。
生命周期管理的实现
ViewModel的作用域会被限定为实例化时使用的ViewModelStoreOwner
,ViewModelStoreOwner
结束生命周期时,ViewModel就会自动回调onCleared
方法用于清理依赖生命周期的工作或者对象。
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
override fun onCleared() {
coroutineScope.cancel()
}
}
在2.5及更高版本的lifecycle库中,ViewModel提供了更多的支持:
- ViewModel可以接受多个
Closeable
对象,ViewModel会在清除时自动调用这些对象的close
方法。 - ViewModel提供了
addCloseable
、setTagIfAbsent
等方法,这些方法允许在任意时刻添加Closeable
对象到ViewModel中,这些对象同样会被自动清除。
下面还是以ComponentActivity
为例看一下清理过程的实现:
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle ();
...
getLifecycle().addObserver(new LifecycleEventObserver () {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
ComponentActivity
会在构造时设置一个lifecycle监听,当activity onDestroy且并非配置改变引起的调用时,执行ViewModelStore的clear
方法清空所有的ViewModel,在清空前,会调用每个ViewModel的clear
方法。
// ViewModelStore
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
// ViewModel
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
// We need the same null check here
if (mCloseables != null) {
synchronized (mCloseables) {
for (Closeable closeable : mCloseables) {
closeWithRuntimeException(closeable);
}
}
}
onCleared();
}
在ViewModel的clear
方法中,对所有保存的Closeable
执行close,然后调用onCleared
。
再看看ktx中的viewModelScope
:
public val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) return scope
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
其本质也是提供了一个实现Closeable
接口的CoroutineScope
,然后通过setTagIfAbsent
设置给ViewModel。
配置变更后仍保持数据的原理
前面在分析ViewModelStore
的获取时,我们知道ComponentActivity
在初始化ViewModelStore
时,会先调用getLastNonConfigurationInstance
,尝试恢复配置未变更前保存的ViewModelStore
。与之对应的也有配置变更时保存ViewModelStore
的逻辑:
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;
}
onRetainNonConfigurationInstance
会在Activity配置发生变更(如横竖屏切换)需要重建时,它会将返回的Object直接保存到ActivityClientRecord
中:
// ActivityThread
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason
) {
...
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
...
}
}
...
}
Activity重建时再设置回去:
// ActivityThread
private 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.activityConfigCallback,
r.assistToken, r.shareableActivityToken);
...
}
// Activity
final void attach(Context context, ActivityThread aThread,
...
NonConfigurationInstances lastNonConfigurationInstances,
...
) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}
既然ViewModelStore实例在重建时被保存和恢复了,那么其中的ViewModel及其状态数据也自然不会变化。
SavedStateHandle实现原理
💡 SavedStateHandle机制是在jetpack-savedstate的基础上实现的,强烈建议先了解此组件的使用方法。
前面我们在分析ComponentActivity
中提供的默认工厂SavedStateViewModelFactory
时,提到了工厂会在需要使用SavedStateHandle
调用createSavedStateHandle
创建实例:
public fun CreationExtras.createSavedStateHandle(): SavedStateHandle {
val savedStateRegistryOwner = this[SAVED_STATE_REGISTRY_OWNER_KEY]
?: throw IllegalArgumentException(...)
val viewModelStateRegistryOwner = this[VIEW_MODEL_STORE_OWNER_KEY]
?: throw IllegalArgumentException(...)
val defaultArgs = this[DEFAULT_ARGS_KEY]
val key = this[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
?: throw IllegalArgumentException(...)
return createSavedStateHandle(
savedStateRegistryOwner, viewModelStateRegistryOwner, key, defaultArgs
)
}
这里会从CreationExtras中获取一些必要参数:
-
savedStateRegistryOwner
:SavedStateRegistryOwner
是jetpack-savedstate库中的一个接口,实现此接口的类表明可以在应用意外销毁时支持保存/恢复状态。jetpack中的ComponentActivity
与Fragment
实现了它。 -
viewModelStateRegistryOwner
:这里实际获取的是当前的ViewModelStoreOwner,工厂会将创建出来的SavedStateHandle实例保存在一个专门的ViewModel—SavedStateHandlesVM
中以加快访问。 -
defaultArgs
:传递给SavedStateHandle的默认参数。 -
key
:与待创建ViewModel相关联的键,用于从SavedStateHandlesVM
中存取SavedStateHandle
实例。
private fun createSavedStateHandle(
savedStateRegistryOwner: SavedStateRegistryOwner,
viewModelStoreOwner: ViewModelStoreOwner,
key: String, defaultArgs: Bundle?
): SavedStateHandle {
val provider = savedStateRegistryOwner.savedStateHandlesProvider
val viewModel = viewModelStoreOwner.savedStateHandlesVM
// If we already have a reference to a previously created SavedStateHandle
// for a given key stored in our ViewModel, use that. Otherwise, create
// a new SavedStateHandle, providing it any restored state we might have saved
return viewModel.handles[key]
?: SavedStateHandle.createHandle(
provider.consumeRestoredStateForKey(key), defaultArgs
).also { viewModel.handles[key] = it }
}
这里获取到的SavedStateProvider
专用于保存/恢复SavedStateHandle
中状态,这个Provider在ComponentActivity
的构造时通过enableSavedStateHandles
创建。
当savedStateHandlesVM没有缓存的实例时,就创建一个新实例返回,这里会先根据key从SavedStateProvider
中读取之前保存的状态作为SavedStateHandle构造过程的参数。这样就实现了数据的恢复。
接下来看看数据的保存,我们知道SDK中Activity的状态保存是靠onSaveInstanceState
回调实现的,SavedStateHandle也不例外:
// ComponentActivity
protected void onSaveInstanceState(@NonNull Bundle outState) {
...
mSavedStateRegistryController.performSave(outState);
}
// SavedStateRegistryController
fun performSave(outBundle: Bundle) {
savedStateRegistry.performSave(outBundle)
}
//SavedStateRegistry
fun performSave(outBundle: Bundle) {
....
// 这里获取到所有注册的SavedStateProvider,调用他们的saveState获取到
// 需要保存的数据,统一的保存到onSaveInstanceState传入的Bundle中
val it: Iterator<Map.Entry<String, SavedStateProvider>> =
this.components.iteratorWithAdditions()
while (it.hasNext()) {
val (key, value) = it.next()
components.putBundle(key, value.saveState())
}
if (!components.isEmpty) {
outBundle.putBundle(SAVED_COMPONENTS_KEY, components)
}
}
用于保存SavedStateHandle中状态的SavedStateHandlesProvider
,则早在Activity初始化时注册到了SavedStateRegistry中。来看看它的saveState
方法:
private val viewModel by lazy { viewModelStoreOwner.savedStateHandlesVM }
override fun saveState(): Bundle {
return Bundle().apply {
...
viewModel.handles.forEach { (key, handle) ->
val savedState = handle.savedStateProvider().saveState()
if (savedState != Bundle.EMPTY) {
putBundle(key, savedState)
}
}
}
}
这里的ViewModel就是之前提到的用来保存所有SavedStateHandle实例的SavedStateHandlesVM
。
SavedStateHandle
内部持有一个SavedStateProvider
,在保存数据时,会将调用它的onSave方法将SavedStateHandle
内部的状态打包成一个Bundle:
private val savedStateProvider = SavedStateRegistry.SavedStateProvider {
...
// Convert the Map of current values into a Bundle
val keySet: Set<String> = regular.keys
val keys: ArrayList<String> = ArrayList(keySet.size)
val value: ArrayList<Any?> = ArrayList(keys.size)
for (key in keySet) {
keys.add(key)
value.add(regular[key])
}
bundleOf(SavedStateHandle.KEYS to keys, SavedStateHandle.VALUES to value)
}
总结
ViewModel是Android Jetpack架构组件之一,它可以帮助我们解决Activity/Fragment等组件在配置更改时数据丢失的问题。通过创建ViewModel对象,我们可以将数据存储在其中,从而实现数据的持久化。ViewModel的使用非常灵活,我们可以将其与LiveData、Kotlin协程等其他组件一起使用,以实现更加强大的功能。在本文中,我们介绍了ViewModel的优势、简单的使用方法,并对主要功能的实现原理进行了分析。如果你有任何疑问,欢迎评论我们一起讨论。