12. ViewModel与SavedStateHandle:进程被杀后数据恢复

说实话,做Android开发这么多年,我最怕听到的一句话就是:「用户反馈填到一半的表单,切出去回了个消息,回来数据全没了。」

嗯,这其实就是进程被系统杀掉了。你想想看,用户辛辛苦苦填了十几项内容,结果因为内存紧张或者切后台太久,Activity重建了,ViewModel也跟着重建了——数据呢?全丢了。

那怎么办?今天我们就来聊聊 SavedStateHandle 这个救星。

为什么ViewModel本身不能持久化数据?

先搞清楚一个问题:ViewModel 的生命周期是跟着 Activity 走的。Activity 正常旋转屏幕,ViewModel 还在。但一旦进程被系统杀掉,ViewModel 实例就没了。

我刚开始用 ViewModel 时也踩过这个坑。当时做了一个订单填写页面,用户填了十几项,切出去接了个电话,回来页面重置了。用户直接给了个差评……从那以后,我再也不敢把「进程被杀」这件事当小概率事件了。

说白了,ViewModel 只是帮你扛住了配置变更(比如屏幕旋转),但扛不住进程被杀的「灭顶之灾」。

SavedStateHandle 是什么?

SavedStateHandle 是 Android 官方提供的一个工具,专门用来解决「进程被杀后数据恢复」的问题。它本质上是一个 Bundle 的封装,但用起来比 Bundle 舒服得多。

它的核心原理是:

  • 系统在杀死进程前,会把 SavedStateHandle 里的数据保存到 Bundle 中
  • 进程重建时,系统把 Bundle 传回来,SavedStateHandle 自动恢复数据
  • 整个过程对开发者几乎是透明的

关键点:SavedStateHandle 是 ViewModel 的构造参数,由系统自动注入。你不需要手动创建它。

怎么用?三步走

第一步,在 ViewModel 的构造函数里接收 SavedStateHandle:

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

    // 用 savedStateHandle 来保存表单数据
    val userName = savedStateHandle.getLiveData<String>("user_name", "")
    val userPhone = savedStateHandle.getLiveData<String>("user_phone", "")
    val userEmail = savedStateHandle.getLiveData<String>("user_email", "")
}

第二步,在 Activity 或 Fragment 里正常使用 LiveData:

class FormActivity : AppCompatActivity() {

    private val viewModel: FormViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_form)

        // 观察数据变化
        viewModel.userName.observe(this) { name ->
            binding.etUserName.setText(name)
        }

        // 用户输入时更新数据
        binding.etUserName.setOnTextChangedListener { text ->
            viewModel.userName.value = text
        }
    }
}

第三步,完事。你没看错,就这么简单。

进程被杀后,系统会自动把 savedStateHandle 里的数据存到 Bundle 里。重建时,ViewModel 拿到旧的 savedStateHandle,数据就回来了。

个人建议:SavedStateHandle 适合存「少量、关键」的数据。比如表单字段、当前选中的 Tab、滚动位置等。不要用它存大对象或列表数据,那会拖慢序列化速度。

实战:表单数据持久化

我们做一个完整的例子。假设有一个注册表单,包含用户名、手机号、邮箱、密码四个字段。

先定义 ViewModel:

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

    // 使用 SavedStateHandle 的 LiveData
    val username = savedStateHandle.getLiveData<String>("username", "")
    val phone = savedStateHandle.getLiveData<String>("phone", "")
    val email = savedStateHandle.getLiveData<String>("email", "")
    val password = savedStateHandle.getLiveData<String>("password", "")

    // 验证表单
    fun isFormValid(): Boolean {
        return username.value?.isNotEmpty() == true &&
               phone.value?.isNotEmpty() == true &&
               email.value?.isNotEmpty() == true &&
               password.value?.isNotEmpty() == true
    }

    // 提交表单
    fun submit() {
        // 这里做网络请求
    }
}

然后在 Activity 里绑定:

class RegisterActivity : AppCompatActivity() {

    private val viewModel: RegisterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_register)

        // 双向绑定(这里用简单的方式演示)
        viewModel.username.observe(this) { binding.etUsername.setText(it) }
        viewModel.phone.observe(this) { binding.etPhone.setText(it) }
        viewModel.email.observe(this) { binding.etEmail.setText(it) }
        viewModel.password.observe(this) { binding.etPassword.setText(it) }

        // 监听输入变化
        binding.etUsername.setOnTextChangedListener { 
            viewModel.username.value = it 
        }
        // ... 其他字段同理
    }
}

现在你试试:填一半表单,把 App 切到后台,然后强行杀掉进程(或者用开发者选项里的「不保留活动」)。再打开 App,表单数据还在。

我曾经踩过的坑:SavedStateHandle 默认只支持「可序列化」的数据类型。如果你存了一个自定义对象,记得让它实现 Parcelable 或 Serializable。否则进程被杀后恢复时会直接崩溃。

SavedStateHandle 的更多用法

除了 getLiveData,SavedStateHandle 还提供了其他方法:

方法 说明
getLiveData(key) 获取一个 LiveData,数据变化时自动保存
get(key) 直接获取保存的值,不包装成 LiveData
set(key, value) 直接设置一个值
contains(key) 检查某个 key 是否存在
keys() 获取所有 key 的集合

我个人习惯是:能用 LiveData 就用 LiveData。因为 LiveData 自带生命周期感知,而且数据变化时 UI 会自动更新。省心。

核心知识体系

下面这张图帮你理清 ViewModel、SavedStateHandle 和进程被杀之间的关系:

ViewModel + SavedStateHandle 数据恢复流程 用户操作表单 数据存入 SavedStateHandle UI 正常显示 进程被系统杀死 系统保存 Bundle Activity 重建 ViewModel 重新创建 数据自动恢复 SavedStateHandle 在进程被杀前后自动完成数据的保存与恢复

避坑指南

最后分享几个我实际项目中踩过的坑:

  • 不要存大对象:SavedStateHandle 底层是 Bundle,Bundle 有大小限制(约 1MB)。存大对象会导致 TransactionTooLargeException。
  • 注意默认值:getLiveData 的第二个参数是默认值。进程第一次启动时,这个默认值会被使用。别把默认值设成 null 然后 UI 没做空判断,会崩。
  • 不要和 Room 混用:SavedStateHandle 适合临时数据,Room 适合持久化数据。如果你需要长期保存的数据,直接用 Room,别折腾 SavedStateHandle。
  • 测试要模拟进程被杀:用开发者选项里的「不保留活动」来测试,别只在模拟器上点 Home 键就以为测完了。

好了,关于 SavedStateHandle 的核心用法就这些。说白了,它就是 ViewModel 在进程被杀时的「救命稻草」。用好了,用户填到一半的表单数据再也不会丢了。

公众号:蓝海资料掘金营,微信deep3321