ViewModel的作用
ViewModel 的作用其实很简单,就是在屏幕旋转时,可以让数据继续存留。ViewModel 的生命周期如下图所示,可以看到当屏幕旋转时,并不会影响到 ViewModel。
ViewModel的使用
kotlin
class MyViewModel: ViewModel() {
//回收资源
override fun onCleared() {
super.onCleared()
}
}
val viewModel: MyViewModel by viewModels<MyViewModel>()
ViewModel 的使用也很简单。如上面的代码所示,我们继承 ViewModel 类,在 Activity中通过 viewModels
扩展方法来获取对应的对象,最后当 Activity 销毁时在 onCleared
方法中释放资源。
如果你希望在ViewModel中使用Context,则可以使用 AndroidViewModel 类,它继承自 ViewModel ,并接收Application 作为 Context。代码如下:
kotlin
//AndroidViewModel 相比 ViewModel 多了一个 Application
class MyAndroidViewModel(application: Application): AndroidViewModel(application) {
//回收资源
override fun onCleared() {
super.onCleared()
}
}
val androidViewModel: MyAndroidViewModel by viewModels<MyAndroidViewModel>()
如果你的 ViewModel 有其他依赖,这时就需要自定义 Factory。最简单的方式就是给自定义的 Factory 加上入参,代码示例如下:
kotlin
val viewModel: MyViewModel by viewModels<MyViewModel>(
factoryProducer = { MyViewModelFactory("tag") } //设置自定义的 Factory
)
但是这种方式代码太耦合了, Google 更推荐使用 CreationExtras
的方式。代码示例如下:
kotlin
//MyViewModel 需要 tag 作为入参
class MyViewModel(val tag: String): ViewModel() {
companion object {
private object Tag: CreationExtras.Key<String>
//CreationExtras 中的 Key
val TAG_KEY: CreationExtras.Key<String> = Tag
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val tag = this[TAG_KEY] ?: ""
MyViewModel(tag)
}
}
}
}
val viewModel: MyViewModel by viewModels<MyViewModel>(
//设置 CreationExtras 的参数,作为入参
extrasProducer = { MutableCreationExtras().also { it.set(MyViewModel.TAG_KEY, "tag") }},
//设置自定义的 Factory
factoryProducer = { MyViewModel.Factory }
)
在实际开发中,经常会遇到两个Fragment之间有通信的需求。这时我们可以通过共享 ViewModel 来实现这一个功能。
首先我们先增加依赖:
scss
implementation("androidx.fragment:fragment-ktx:1.6.2")
然后我们就可以使用 activityViewModels
来获取当前 Activity 下的指定的 ViewModel。注意不能使用 viewModels
,不然获取的 ViewModel 是其对应 Fragment 下的对象,而不是同一个。代码示例如下:
kotlin
//可以获取到同一个 MyViewModel 对象
class AFragment: Fragment() {
val viewModel: MyViewModel by activityViewModels<MyViewModel>()
//不能使用viewModels,不然获取的 ViewModel 是其对应 Fragment 下的对象,而不是同一个
//val viewModel: MyViewModel by viewModels<MyViewModel>()
}
class BFragment: Fragment() {
val viewModel: MyViewModel by activityViewModels<MyViewModel>()
}
Fragment 里面的 viewModels
相对于 Activity 下的多了一个 ownerProducer
参数。这个参数可以指定 ViewModel 的存储地点(即ViewModelStoreOwner对象)。代码如下所示:
kotlin
val viewModel: MyViewModel by viewModels<MyViewModel>(
//存储地点为父Fragment
ownerProducer = { requireParentFragment() },
//存储地点为activity
//ownerProducer = { requireActivity() },
//存储地点为FragmentB()
//ownerProducer = { FragmentB() },
)
ViewModel的原理
如上图所示,当我们第一次通过 viewModels
获取对应的 ViewModel 对象时,其内部首先会先创建 ViewModelProvider
对象, 然后在 Factory
(可以自定义Factory) 使用传入的 Class 来反射获取对应的 ViewModel 实例,最后将实例存储在 ViewModelStore
中。
ViewModelStore
内部是一个 map,通过 key-value 保存 ViewModel 对象。而 ViewModelStoreOwner
只是一个接口,activity 和 Fragment 都实现了它,因此我们可以在activity 和 Fragment 中使用 ViewModel。
当我们下一次获取 ViewModel 时,就会直接从 ViewModelStore
中获取对应的 ViewModel 对象,而不是反射获取了。
上面提到过,ViewModel 最大的特点就是在屏幕旋转时,可以让数据继续存留。它的核心原理是NonConfigurationInstances
类。
如上图所示,ComponentActivity 的 NonConfigurationInstances
类中包含着 ViewModelStore
对象。而 Activity 的 NonConfigurationInstances
类也引用着 ComponentActivity 的 NonConfigurationInstances
类对象。当 Activity 在屏幕旋转情况下重建时,ActivityThread 的 performDestroyActivity 方法中会保存 Activity 的 NonConfigurationInstances
对象到 ActivityClientRecord 中。而当 Activity 重建完成时,ActivityThread 的 performLaunchActivity 方法将会被调用。它会将 Activity 的 NonConfigurationInstances
对象作为参数传入到 attach 方法中去。
ViewModel 与 onSaveInstanceState 的区别
除了 ViewModel,onSaveInstanceState()方法同样可以解决屏幕旋转带来的数据丢失问题。那么它们的区别是什么呢?
- 调用时机上,当系统"未经你许可"时销毁了你的activity时就会调用 onSaveInstanceState,比如A 跳转 B、按下Home键等;而使用ViewModel恢复数据则只有在因配置更改界面销毁重建的情况。
- 存储方式上,onSaveInstanceState 是通过序列化到磁盘的方式,而 ViewModel 是存储在内存中
- 存储数据大小上,ViewModel 是存储在内存中,因此大小限制就是App的可用内存大小。而 onSaveInstanceState 只能存可序列化和反序列化的对象,且大小有限制(一般Bundle限制大小1M)
如果你想要
ViewModel
感知onSaveInstanceState
的触发,可以使用SavedStateHandle
组件。这里先不详细介绍,在下一篇 LiveData 的文章中在介绍它。
参考
- App architecture | Android Developers (google.cn)
- GitHub - android/nowinandroid: A fully functional Android app built entirely with Kotlin and Jetpack Compose
- "终于懂了"系列:Jetpack AAC完整解析(三)ViewModel 完全掌握!
- ActivityThread.java - Android Code Search
- ActivityThread.java - Android Code Search
- SavedStateHandle组件解析 - 掘金 (juejin.cn)