32、ViewModel与LiveData:ViewModel的创建、LiveData的观察、数据持久化

说到Android架构组件,ViewModel和LiveData绝对是最常用的两个。我刚开始接触它们时,其实有点懵——不就是存个数据、观察个变化吗?用得着搞这么复杂?后来在项目里踩了几次坑,才真正体会到它们的设计精妙之处。

今天我们就来聊聊这两个组件。我会从实际开发的角度出发,把ViewModel的创建、LiveData的观察,以及数据持久化这些事儿讲清楚。

ViewModel:为什么需要它?

先问一个问题:你的Activity或Fragment旋转屏幕后,数据还在吗?

答案是不在。因为旋转屏幕会导致Activity重建,所有成员变量都会被重置。你想想看,用户填了一半的表单,转个屏就没了,这体验能好吗?

ViewModel就是为了解决这个问题而生的。它会在Activity重建时存活下来,数据不会丢失。

核心要点:ViewModel的生命周期独立于Activity/Fragment的配置变化。它会在Activity被销毁(比如finish()调用)时才真正清理。

创建ViewModel:两种方式

我个人习惯用委托方式创建ViewModel,代码更简洁。但传统方式也得会,毕竟有些老项目还在用。

方式一:传统方式

class MyViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>(0)
    val count: LiveData<Int> get() = _count

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

// 在Activity中使用
class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this)[MyViewModel::class.java]
    }
}

方式二:委托方式(推荐)

class MainActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 直接用viewModel,不用手动创建
    }
}

嗯,这里要注意:by viewModels()需要引入activity-ktx依赖。我在项目中遇到过有人忘了加依赖,结果编译报错,排查了半天。

LiveData:观察数据变化

LiveData说白了就是一个可观察的数据容器。它和普通的观察者模式最大的区别是——它感知生命周期。

什么意思呢?就是当Activity在前台时,LiveData才会通知观察者。如果Activity已经销毁,它自动取消订阅。这能有效避免内存泄漏。

小技巧:LiveData的观察回调只在主线程执行。所以更新数据时,如果是在子线程,记得用postValue()而不是setValue()

观察LiveData

viewModel.count.observe(this) { value ->
    textView.text = "当前计数:$value"
}

这段代码看起来简单,但有个坑:observe()的第一个参数是LifecycleOwner。如果你传了this,那LiveData就会跟着Activity的生命周期走。我曾经在Fragment里传了Activity的LifecycleOwner,结果Fragment销毁了观察还在,导致空指针——这坑我踩过。

数据持久化:不仅仅是内存

ViewModel默认只在内存中保存数据。如果进程被系统杀死了,数据还是会丢。那怎么办?

两种思路:

  • SavedStateHandle:ViewModel的扩展,能在进程被杀后恢复数据
  • 结合Room或DataStore:把数据持久化到本地数据库或文件

使用SavedStateHandle

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

    val count: LiveData<Int> = savedStateHandle.getLiveData("count", 0)

    fun increment() {
        savedStateHandle["count"] = (savedStateHandle["count"] ?: 0) + 1
    }
}

这种方式的好处是:系统自动帮你序列化和反序列化。但注意,它只能存简单类型,复杂对象还是得用Room。

警告:SavedStateHandle默认使用Bundle存储,有大小限制(约1MB)。别往里面塞大对象,否则会崩溃。

知识体系总览

下面这张图是我自己整理的ViewModel与LiveData的核心逻辑,你看一眼就能明白它们之间的关系:

ViewModel与LiveData核心架构 Activity / Fragment ViewModel LiveData SavedStateHandle Room / DataStore Bundle(进程级) 绿色箭头:生命周期感知 | 橙色箭头:数据流向

避坑指南

最后分享几个我踩过的坑,希望能帮你少走弯路:

  • 不要在ViewModel里持有Activity的Context——这会导致内存泄漏。如果需要Context,用AndroidViewModel,它持有的是Application Context。
  • LiveData的粘性事件——如果你用LiveData做一次性事件(比如弹Toast),要注意它可能会重复触发。我一般用SingleLiveEventEventWrapper来解决。
  • ViewModel的Factory——如果ViewModel需要自定义参数,记得写一个Factory。我曾经偷懒用默认构造,结果参数传不进去,调试了半天。

我的习惯:每个页面都创建一个对应的ViewModel,哪怕它现在只存一个变量。因为需求总会变,到时候再拆就麻烦了。

好了,关于ViewModel和LiveData的核心用法就聊到这儿。记住一句话:ViewModel管数据存活,LiveData管数据通知,两者配合,你的页面状态管理会轻松很多。


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