Android ViewModel 数据持久化实战:告别屏幕旋转导致的数据丢失
你是否曾在调试 Android 应用时,满怀信心地填写完一个复杂表单,轻轻旋转了一下手机,然后眼睁睁看着所有输入的数据瞬间消失,只留下一个空白的界面和一颗崩溃的心?这种场景对于许多 Android 开发者来说,简直是日常开发中的“经典噩梦”。屏幕旋转、系统语言切换、深色模式切换等配置变更,都会导致 Activity 或 Fragment 被销毁并重建,而传统的 onSaveInstanceState() 机制只能保存少量、简单的数据,对于复杂的业务状态往往力不从心。
这正是 ViewModel 诞生的核心使命之一。它不仅仅是架构组件库中的一个普通类,更是解决 Android 应用数据生命周期管理难题的一把利器。本文将从一个资深开发者的实战视角,深入剖析 ViewModel 如何优雅地解决数据在配置变更时的持久化问题,并揭示那些官方文档中未曾明说的“坑”与最佳实践。
1. ViewModel 的生命周期:超越 Activity/Fragment 的生存智慧
要理解 ViewModel 如何解决数据丢失问题,首先必须彻底搞懂它的生命周期。很多人误以为 ViewModel 是“全局单例”或“静态变量”的替代品,这种理解是片面的,也是危险的。
1.1 生命周期对比:ViewModel vs Activity
让我们通过一个简单的代码实验来直观感受两者的差异。创建一个包含计时器的 Activity 和对应的 ViewModel:
// 一个简单的计时器 ViewModel
class TimerViewModel : ViewModel() {
private var startTime: Long = System.currentTimeMillis()
fun getElapsedTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel: TimerViewModel by viewModels()
private var activityCreatedTime = System.currentTimeMillis()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("LifecycleDemo",
"Activity创建时间: ${System.currentTimeMillis() - activityCreatedTime}ms")
Log.d("LifecycleDemo",
"ViewModel计时: ${viewModel.getElapsedTime()}ms")
}
}
现在,启动应用后立即旋转屏幕,观察日志输出:
// 首次启动
D/LifecycleDemo: Activity创建时间: 0ms
D/LifecycleDemo: ViewModel计时: 0ms
// 旋转屏幕后
D/LifecycleDemo: Activity创建时间: 0ms // Activity是全新的实例
D/LifecycleDemo: ViewModel计时: 2500ms // ViewModel还是原来那个!
关键发现:Activity 被重建了,但 ViewModel 实例保持不变,其中的 startTime 仍然是第一次创建时的时间戳。这就是 ViewModel 在配置变更时保持数据不丢失的魔法所在。
1.2 ViewModelStoreOwner:理解作用域的关键
ViewModel 并非全局存在,它被限定在特定的 ViewModelStoreOwner 作用域内。最常见的 ViewModelStoreOwner 就是 Activity 和 Fragment。
| 作用域类型 | 获取方式 | ViewModel 销毁时机 |
|---|---|---|
| Activity | by viewModels() |
Activity 被 finish() 且不会重建时 |
| Fragment | by viewModels() |
Fragment 被永久移除时 |
| Activity 共享给 Fragment | by activityViewModels() |
所属 Activity 被销毁时 |
| Navigation 目标 | by navGraphViewModels(R.id.nav_graph) |
从返回栈中弹出时 |
这里有一个常见的误区:Fragment 中使用 by viewModels() 获取的 ViewModel,其作用域是这个 Fragment 本身,而不是宿主 Activity。这意味着如果 Fragment 被替换或移除,这个 ViewModel 就会被清除。如果需要在多个 Fragment 间共享数据,应该使用 by activityViewModels()。
注意:ViewModel 虽然能存活于配置变更,但它不是永久存储方案。当用户按返回键退出应用,或者系统因内存不足杀死进程时,ViewModel 中的数据依然会丢失。对于需要持久化的数据,应该结合 Room、DataStore 等持久化方案。
2. 那些年我们踩过的 ViewModel 坑:从内存泄漏到数据错乱
理解了基本概念后,让我们看看在实际项目中,开发者们最容易掉进去的几个“坑”。
2.1 坑一:在 ViewModel 中持有 Context 引用
这是最经典的内存泄漏场景。看下面这个有问题的代码:
// ❌ 危险!可能导致内存泄漏
class BadViewModel(private val context: Context) : ViewModel() {
private val sharedPrefs = context.getSharedPreferences("app", MODE_PRIVATE)
fun saveData(key: String, value: String) {
sharedPrefs.edit().putString(key, value).apply()
}
}
问题在于:如果传入的是 Activity 的 Context,而 ViewModel 的生命周期比 Activity 长(在配置变更时),就会导致 Activity 实例无法被回收。
解决方案:
- 如果需要 Application Context,使用
AndroidViewModel - 使用
Application的 Context,它不会导致内存泄漏 - 通过参数传入所需数据,而不是 Context 本身
// ✅ 正确做法:使用 AndroidViewModel
class SafeViewModel(application: Application) : AndroidViewModel(application) {
private val sharedPrefs = application.getSharedPreferences("app", MODE_PRIVATE)
fun saveData(key: String, value: String) {
sharedPrefs.edit().putString(key, value).apply()
}
}
// ✅ 或者:通过参数传入所需数据
class BetterViewModel(private val repository: UserRepository) : ViewModel() {
// 不直接持有 Context,而是通过 Repository 访问数据
}
2.2 坑二:误用 LiveData 的 observe 方法
LiveData 是 ViewModel 的好搭档,但用错了地方也会出问题:
class ProblematicViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> = _userData
fun loadUser() {
viewModelScope.launch {
val user = repository.loadUser()
_userData.value = user // 或者 postValue(user)
}
}
}
// 在 Activity/Fragment 中观察
class MainActivity : AppCompatActivity() {
private val viewModel: ProblematicViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ❌ 问题:每次 onCreate 都会添加新的观察者
viewModel.userData.observe(this) { user ->
updateUI(user)
}
}
}
每次配置变更后,Activity 重建,都会添加一个新的观察者。虽然 LiveData 会立即将最新值推送给新观察者,但如果有多个观察者,可能会导致 UI 更新多次。
解决方案:使用 observe 的 LifecycleOwner 参数确保观察者自动清理
// ✅ 正确:LiveData 会自动管理观察者的生命周期
// 当 Activity 销毁时,观察者会自动移除
viewModel.userData.observe(this) { user ->
updateUI(user)
}


795

被折叠的 条评论
为什么被折叠?



