4、ViewModel:ViewModel的创建与作用域、SavedStateHandle、ViewModelProvider.Factory
好,咱们今天来聊聊 ViewModel。说实话,这个组件是 Jetpack 里我最早爱上的一个。为什么?因为它解决了 Android 开发里一个老大难问题——屏幕旋转时数据丢失。
你想想看,以前我们写 Activity,一旋转手机,整个界面重建,所有临时数据全没了。要么用 onSaveInstanceState 存到 Bundle 里,要么干脆不让旋转。这两种方式都不优雅。ViewModel 的出现,算是把这个痛点彻底解决了。
ViewModel 的创建与作用域
ViewModel 的核心思想很简单:把数据从 Activity/Fragment 的生命周期中剥离出来。它会在 Activity 或 Fragment 对应的生命周期范围内保持存活。只要 Activity 没真正 finish,ViewModel 就一直存在。
我习惯用 by viewModels() 这个委托来创建,代码最简洁:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.loadData()
}
}
这里有个关键点:作用域。ViewModel 的作用域取决于你获取它的方式:
- Activity 作用域:在 Activity 里用 viewModels(),ViewModel 会跟随 Activity 的生命周期。
- Fragment 作用域:在 Fragment 里用 viewModels(),ViewModel 只跟随当前 Fragment。
- 共享作用域:在 Fragment 里用 activityViewModels(),多个 Fragment 可以共享同一个 ViewModel。
我在项目中遇到过这样一个场景:一个详情页里有三个 Tab Fragment,它们需要共享一份商品数据。如果每个 Fragment 各自创建 ViewModel,数据就不同步了。用 activityViewModels() 就完美解决。
核心要点:ViewModel 的生命周期比 Activity/Fragment 更长。它会在 Activity 的 onDestroy(非旋转导致的)时自动清理。所以千万别在 ViewModel 里持有 Activity 的引用,否则会内存泄漏。
SavedStateHandle:让 ViewModel 也能保存状态
好,问题来了。ViewModel 虽然能扛住屏幕旋转,但如果进程被系统杀了呢?比如用户切到后台,系统内存不足把你的 App 干掉了。这时候 ViewModel 也会消失,数据就丢了。
以前我们得手动在 onSaveInstanceState 里存数据,然后在 onCreate 里恢复。麻烦不说,还容易遗漏。
SavedStateHandle 就是来解决这个问题的。它本质上是一个 自动化的 Bundle 存储机制。你只需要在 ViewModel 的构造函数里声明它,框架会自动帮你保存和恢复数据。
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 使用 SavedStateHandle 保存数据
val userName = savedStateHandle.getLiveData<String>("user_name", "")
fun updateName(name: String) {
savedStateHandle["user_name"] = name
}
}
嗯,这里要注意:SavedStateHandle 只能保存 Bundle 支持的数据类型。像自定义对象、大图片这些,你得自己序列化。我一般会把复杂对象转成 JSON 字符串再存。
小技巧:SavedStateHandle 还支持 getStateFlow() 方法,如果你用 Kotlin Flow 做数据流,可以直接拿到 StateFlow,省去手动转换的麻烦。
我曾经踩过一个坑:在 ViewModel 里同时用了 SavedStateHandle 和 Room 数据库。结果发现 SavedStateHandle 里存的数据和数据库里的不一致。后来才意识到,SavedStateHandle 只在进程被杀时恢复,而数据库是持久化的。所以我的建议是:临时状态用 SavedStateHandle,持久数据用 Room,别混着用。
ViewModelProvider.Factory:自定义创建逻辑
默认情况下,ViewModel 是通过无参构造函数创建的。但很多时候,我们需要给 ViewModel 传参数,比如 Repository、Application 上下文等。这时候就需要 ViewModelProvider.Factory 了。
说白了,Factory 就是一个 工厂模式,让你能控制 ViewModel 的创建过程。
class MyViewModelFactory(
private val repository: MyRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MyViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
使用的时候也很简单:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels {
MyViewModelFactory(MyRepository())
}
}
如果你用 Hilt 或 Koin 做依赖注入,其实很少需要手写 Factory。但理解它的原理很重要。我记得有一次排查一个 Bug,发现 ViewModel 里的数据总是旧的,查了半天才发现是 Factory 里每次都 new 了一个新的 Repository 实例,导致数据不同步。改成单例就好了。
避坑指南:千万不要在 Factory 里持有 Activity 或 Fragment 的引用。我曾经这么干过,结果导致内存泄漏,界面销毁了 ViewModel 还活着,Activity 对象却释放不掉。正确的做法是只传数据层对象,比如 Repository、UseCase 这些。
知识体系总览
下面这张图是我整理的 ViewModel 核心知识结构,你可以对照着回顾一下:
总结一下
ViewModel 这三个知识点其实是一条线:创建方式决定了作用域,SavedStateHandle 解决了进程被杀的问题,Factory 让你能灵活传参。实际开发中,我建议你:
- 能用 by viewModels() 就别手写 Factory,除非需要传参
- 需要跨 Fragment 共享数据时,优先考虑 activityViewModels()
- 涉及用户输入或临时状态,用 SavedStateHandle 兜底
- 复杂数据持久化,交给 Room 或 DataStore
嗯,这些就是我这些年用 ViewModel 总结出来的经验。说白了,ViewModel 本身不复杂,但用好了能让你的代码结构清晰很多。下次咱们聊聊 LiveData 和 Flow 怎么选,那个话题更有意思。
公众号:蓝海资料掘金营,微信deep3321