4、ViewModel 与状态管理:ViewModel的生命周期,如何跨Fragment共享数据,以及SavedStateHandle的使用

好,咱们今天来聊聊 ViewModel。说实话,这个组件是 Jetpack 全家桶里我最早爱上的一个。为什么?因为它解决了 Android 开发里一个老大难问题——屏幕旋转。

你想想看,以前没有 ViewModel 的时候,转一下屏幕,Activity 重建,数据全没了。要么用 onSaveInstanceState 存 Bundle,要么自己搞个 retained fragment 来保命。这两种方式我都用过,说实话,体验都不太好。Bundle 能存的东西有限,retained fragment 又容易造成内存泄漏。

ViewModel 的出现,说白了就是帮我们优雅地解决这个问题。

ViewModel 的生命周期

ViewModel 的生命周期和 Activity 或 Fragment 不一样。它不跟着屏幕旋转走,而是跟着 LifecycleOwner 的销毁逻辑走。

具体来说:

  • Activity 被 finish() 了,ViewModel 才销毁
  • Fragment 被 detach 了,ViewModel 才销毁
  • 屏幕旋转?ViewModel 活得好好的

我画个简单的生命周期对比图:

场景 Activity 生命周期 ViewModel 生命周期
首次创建 onCreate → onStart → onResume 初始化
屏幕旋转 onDestroy → onCreate 保持不变
返回键退出 onDestroy onCleared()

嗯,这里要注意一点:ViewModel 的 onCleared() 方法,是它唯一能感知到的生命周期回调。我习惯在这里做一些资源释放的工作,比如取消协程、关闭数据库连接等。

核心要点:ViewModel 的生命周期比 Activity/Fragment 更长,所以千万别在 ViewModel 里持有 Activity 或 View 的引用,否则会造成内存泄漏。

如何跨 Fragment 共享数据

这个场景我遇到过很多次。比如一个主界面,左边是列表 Fragment,右边是详情 Fragment。用户点击列表项,详情页要更新。怎么通信?

以前的做法:接口回调、EventBus、广播…… 这些方式我都用过,但维护起来真的很痛苦。

ViewModel 提供了一种更优雅的方式——作用域共享

原理很简单:两个 Fragment 共用同一个 Activity 的 ViewModel。只要它们获取 ViewModel 时传入的是同一个 Activity 实例,那它们拿到的就是同一个 ViewModel 对象。

看代码:

// 在 Fragment A 中
val sharedViewModel: SharedViewModel by viewModels(
    ownerProducer = { requireActivity() }
)

// 在 Fragment B 中
val sharedViewModel: SharedViewModel by viewModels(
    ownerProducer = { requireActivity() }
)

注意看 ownerProducer 参数。我传的是 requireActivity(),这意味着这两个 Fragment 拿到的 ViewModel 是同一个 Activity 作用域下的。数据自然就共享了。

我个人习惯在 ViewModel 里用 LiveData 或 StateFlow 来暴露数据:

class SharedViewModel : ViewModel() {
    private val _selectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> = _selectedItem

    fun selectItem(item: Item) {
        _selectedItem.value = item
    }
}

Fragment A 调用 selectItem(),Fragment B 观察 selectedItem,数据就流动起来了。简单、干净、可测试。

小技巧:如果 Fragment 层级很深,或者你想让 ViewModel 的作用域更精确,可以考虑用 Navigation 的 navGraphViewModels()。这样 ViewModel 的生命周期就和导航图绑定在一起了。

SavedStateHandle 的使用

好,接下来聊一个我踩过坑的东西——SavedStateHandle。

ViewModel 虽然能扛住屏幕旋转,但扛不住进程被杀死。比如用户把 App 切到后台,系统内存不足把你的进程杀了。等用户再回来,ViewModel 重建了,数据全没了。

这时候就需要 SavedStateHandle 出马了。

它本质上是一个 Bundle 的封装,但用起来比 onSaveInstanceState 方便得多。你直接在 ViewModel 的构造函数里声明它就行:

class MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // 直接读写,像操作 Map 一样
    val count = savedStateHandle.getLiveData<Int>("count", 0)

    fun increment() {
        count.value = (count.value ?: 0) + 1
    }
}

你看,不需要手动调用 onSaveInstanceState,也不需要手动恢复。SavedStateHandle 会自动帮你保存和恢复数据。

我曾经在一个电商项目里用过它。用户选了半天商品,填了一堆表单,结果 App 被系统杀了。回来之后,所有数据都还在。用户根本不知道发生过什么。体验非常好。

注意:SavedStateHandle 能存的数据类型有限制,和 Bundle 一样。复杂对象需要实现 Parcelable 或 Serializable。我建议只存一些关键的状态数据,比如用户 ID、当前步骤等。大量数据还是走 Room 或 DataStore 更靠谱。

另外,如果你用 Hilt 或 Koin 做依赖注入,需要额外配置一下。以 Hilt 为例:

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: MyRepository
) : ViewModel() {
    // ...
}

Hilt 会自动帮你注入 SavedStateHandle,不需要额外处理。

总结一下

ViewModel 的核心价值就三点:

  1. 生命周期感知——自动处理屏幕旋转等配置变更
  2. 数据共享——通过作用域实现 Fragment 间通信
  3. 状态保存——SavedStateHandle 帮你扛住进程被杀

说实话,这三个能力组合起来,基本覆盖了日常开发中 90% 的状态管理需求。我现在的项目里,几乎每个页面都会用 ViewModel。它已经成了我写 Android 代码的标配。

下一章咱们聊聊 LiveData 和 Flow 的选择。嗯,这个话题也挺有意思的,到时候见。