ViewModel的谎言:为什么你的现代 Android 应用脆弱
Source: Dev.to

我们都有过这种经历。你用最新的 Jetpack 库构建了一个漂亮的应用。你使用 MVVM,代码整洁,ViewModel 负责处理数据。你在设备上测试,一切运行完美。于是你觉得自己是个“现代 Android 开发者”。
但随后,有用户留下评论:“这个应用会随机丢失我的数据”
你查看日志。没有崩溃。你尝试复现。没有任何线索。于是你开始怀疑用户说错了。但事实并非如此。你的应用正遭受一种现代架构没有提醒你的沉默性失败:进程死亡。
ViewModel 的安慰
多年来,Google 告诉我们 ViewModel 是解决 Activity 生命周期头疼问题的答案。我们了解到当屏幕旋转时,Activity 会被销毁,但 ViewModel 会继续存在。
于是我们变得自信。我们把所有东西都放进 ViewModel:用户资料、搜索结果、表单数据。我们以为只要应用“在运行”,这些数据就安全了。
这就是谎言。
实际情况是怎样的
在真实世界中,用户不会一直停留在你的应用里。他们会切换到 Instagram,拍摄一张 50 MB 的 4K 照片,玩一款大型游戏。
当这些事情发生时,Android 系统会查看你在后台的应用并想:“我需要那块 RAM。”于是它会立刻杀死你的应用进程。
用户回来时,Android 会体贴地为你重新创建 Activity。但你内存中的 ViewModel 状态呢?它是全新的。所有在内存中的变量都消失了,变成 null,变成空的。于是你的应用瞬间崩溃。
过去的方式是诚实的
在有了 ViewModel 之前,我们使用 onSaveInstanceState。
如果你已经开发了一段时间,你会记得那种痛苦。你必须手动把每个变量打包进 Bundle,然后在 onCreate 中再解包。代码模板繁多,Activity 变得臃肿,而且很容易出错。
但它有一个超能力:它能工作。因为系统强迫你去考虑那个 Bundle,你的应用天然地对进程死亡具有抵抗力。你清楚地知道哪些数据被保存、哪些被恢复。
当 ViewModel 出现时,我们都松了一口气。我们把数据搬进这些闪亮的新容器,停止担心 Bundle。我们想:“ViewModel 现在处理生命周期了,我完事了。”但我们忘记了一个关键细节:
ViewModel 能够在配置更改时存活,但它 不能 在进程死亡时存活。
迎接 SavedStateHandle
几年前,Google 认识到我们都在丢失数据。他们的解决方案是 SavedStateHandle。
脆弱的方式(进程死亡时数据消失)
class ProfileViewModel : ViewModel() {
// 这只是 RAM 中的一个变量。
// 如果系统杀死进程,这个变量会被清除。
var userId: String? = null
}
稳健的做法(数据得以保留)
class ProfileViewModel(private val state: SavedStateHandle) : ViewModel() {
// 这由系统的 Bundle 支持。
// 即使进程死亡,"userId" 仍然安全。
val userId: LiveData = state.getLiveData("user_id")
fun initUser(id: String) {
state["user_id"] = id
}
}
结语
构建一个稳健的应用并不是使用最新的库,而是要尊重 Android OS 的实际工作方式。停止依赖内存,开始信任 SavedStateHandle,你的用户会因为一个真正记得他们的应用而感激你。
库和最佳实践每年都会变化,但 Android OS 管理内存的方式始终如一。我们无法控制系统何时需要回收 RAM,但我们可以控制应用如何从中恢复。