一、LiveData 的设计思想与优势

LiveData 这个名字,说白了就是「活的数据」。它不是一个简单的数据容器,而是一个能感知生命周期的数据持有者。我最早接触它的时候,心里想的是:「这不就是观察者模式套了个生命周期吗?」后来用深了才发现,事情没那么简单。

1.1 设计思想:生命周期感知

传统的观察者模式有个痛点——你得手动管理订阅和取消订阅。比如在 Activity 里注册了一个回调,如果忘了在 onDestroy 里取消,轻则内存泄漏,重则崩溃。LiveData 是怎么解决的呢?它内部维护了一个 LifecycleOwner 的弱引用,当 LifecycleOwner 处于活跃状态(STARTED 或 RESUMED)时,它才会通知观察者。一旦 LifecycleOwner 进入 DESTROYED 状态,LiveData 会自动清理观察者。

嗯,这里要注意:LiveData 并不是「不泄漏」,而是「自动清理」。它的核心思想是——数据应该随着页面的生命周期走,而不是反过来让页面去适配数据。

核心优势总结:
  • 自动生命周期管理——不再需要手动取消注册
  • 数据一致性——只在活跃状态下更新 UI,避免界面闪烁
  • 无内存泄漏——Observer 会随着 LifecycleOwner 的销毁自动移除
  • 共享资源安全——多个 Activity/Fragment 可以共享同一个 LiveData 实例

1.2 为什么不用普通的 Observable?

你想想看,如果用 RxJava 的 Observable,你得在 onPause 里 dispose,在 onResume 里重新订阅。代码写多了,难免有遗漏。我在项目中就遇到过这种情况——一个同事用 RxJava 监听网络状态,结果在 Fragment 销毁后还在更新 UI,直接报了 IllegalStateException。换成 LiveData 后,这种问题再也没出现过。

说白了,LiveData 就是为 Android 生命周期量身定做的。它不需要你操心「什么时候订阅、什么时候取消」,它自己会判断。

二、LiveData 的变换操作(map / switchMap)

实际开发中,我们拿到的数据往往不是 UI 直接需要的。比如从数据库查出来的是 User 对象,但界面上只需要显示用户名。这时候就需要对 LiveData 做变换。

2.1 map:一对一的同步变换

map 是最简单的变换。它接收一个 Function,把 LiveData 里的值转换成另一个值。注意,这个转换是同步的,不能做耗时操作。

val userLiveData: LiveData<User> = ...
val userNameLiveData: LiveData<String> = Transformations.map(userLiveData) { user ->
    user.name
}

我在项目中经常用 map 来做数据脱敏。比如手机号只显示后四位,或者金额保留两位小数。这些操作不涉及异步,用 map 最合适。

小提示:map 里的 lambda 是在主线程执行的,所以别在里面做数据库查询或者网络请求。如果要做异步操作,用 switchMap。

2.2 switchMap:一对多的异步变换

switchMap 和 map 的区别在于:map 返回的是新值,而 switchMap 返回的是一个新的 LiveData。它常用于「根据一个 LiveData 的值,去触发另一个 LiveData 的查询」的场景。

举个例子:用户输入搜索关键词,你需要根据关键词去数据库查询结果。

val queryLiveData: LiveData<String> = ...
val searchResultLiveData: LiveData<List<Result>> = Transformations.switchMap(queryLiveData) { query ->
    repository.search(query)  // 返回 LiveData<List<Result>>
}

switchMap 有个关键特性——「切换」。当 queryLiveData 的值发生变化时,它会取消对上一个 LiveData 的订阅,转而订阅新的 LiveData。这避免了旧查询结果覆盖新结果的问题。我曾经在项目中踩过这个坑:用 map 去触发查询,结果两次查询的返回顺序不一致,导致 UI 显示错误。换成 switchMap 后,问题迎刃而解。

注意:switchMap 每次都会创建一个新的 LiveData,如果频繁触发,可能会产生大量对象。建议在 ViewModel 里做防抖处理,或者结合 MediatorLiveData 做更精细的控制。

三、MediatorLiveData:多数据源合并

MediatorLiveData 是 LiveData 的增强版。它可以同时观察多个 LiveData 源,并在任何一个源发生变化时,触发自己的更新。说白了,它就是一个「数据合并器」。

3.1 典型场景:多个数据源合并

比如你的页面需要同时展示用户信息和系统通知。这两个数据来自不同的 LiveData,但 UI 需要等两个数据都准备好才能渲染。

val userLiveData: LiveData<User> = ...
val notificationLiveData: LiveData<Notification> = ...

val combinedLiveData = MediatorLiveData<CombinedData>().apply {
    var user: User? = null
    var notification: Notification? = null

    fun update() {
        val u = user ?: return
        val n = notification ?: return
        value = CombinedData(u, n)
    }

    addSource(userLiveData) { userData ->
        user = userData
        update()
    }

    addSource(notificationLiveData) { notif ->
        notification = notif
        update()
    }
}

这个模式我用了很多次。它比用 LiveData 的 map 或者 switchMap 更灵活,因为你可以控制「什么时候才触发更新」。比如上面的例子,只有当两个数据都非空时,才发射合并后的值。

3.2 动态添加和移除数据源

MediatorLiveData 还支持动态添加和移除数据源。这在某些场景下非常有用,比如根据用户权限动态决定监听哪些数据。

val mediatorLiveData = MediatorLiveData<String>()

fun addSourceIfNeeded(source: LiveData<String>) {
    if (!mediatorLiveData.hasActiveObservers()) {
        mediatorLiveData.addSource(source) { value ->
            mediatorLiveData.value = value
        }
    }
}

fun removeSource(source: LiveData<String>) {
    mediatorLiveData.removeSource(source)
}

嗯,这里要注意:removeSource 之后,之前注册的 Observer 也会被移除。如果你只是暂时不想监听,可以考虑用 switchMap 配合一个开关 LiveData 来实现。

四、LiveData 在事件总线中的正确用法

很多人喜欢用 LiveData 做事件总线,比如 EventBus 的替代品。但这里有个大坑——LiveData 会「粘性」地发射数据。什么意思呢?就是当你注册一个 Observer 时,如果 LiveData 已经有值了,它会立刻把这个值发射给你。这在事件总线里往往是灾难。

4.1 粘性事件的问题

举个例子:你在 Activity A 里发送了一个「跳转到详情页」的事件,然后 Activity B 才注册监听。结果 Activity B 一启动,就收到了这个旧事件,直接跳转了。这显然不是你想要的行为。

我曾经在项目里用 LiveData 做全局的登录状态监听,结果每次 Fragment 重建时都会收到上一次的登录状态,导致 UI 闪烁。后来我改用 SingleLiveEvent 解决了这个问题。

4.2 解决方案:SingleLiveEvent

SingleLiveEvent 是一个自定义的 LiveData,它保证每个事件只被消费一次。实现原理很简单:在 setValue 时标记为未消费,在 observe 时只发射未消费的事件。

class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val pending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner) { value ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(value)
            }
        }
    }

    override fun setValue(value: T?) {
        pending.set(true)
        super.setValue(value)
    }
}

使用方式很简单:

val eventLiveData = SingleLiveEvent<String>()

// 发送事件
eventLiveData.value = "打开详情页"

// 接收事件(只会收到一次)
eventLiveData.observe(this) { event ->
    // 处理事件
}
最佳实践:
  • 用 SingleLiveEvent 替代普通 LiveData 做事件总线
  • 如果事件需要被多个 Observer 消费,考虑用 SharedFlow 替代
  • 不要在 ViewModel 外部暴露 MutableLiveData,用 LiveData 暴露只读版本

4.3 更现代的方案:SharedFlow

如果你用的是 Kotlin 协程,SharedFlow 是比 LiveData 更合适的事件总线方案。它天然支持「只发射给新订阅者」的模式,而且没有生命周期绑定的限制。不过,如果你的项目还在用 LiveData 体系,SingleLiveEvent 已经足够用了。

我个人习惯是:UI 层的数据展示用 LiveData,跨页面的事件传递用 SharedFlow。这样既利用了 LiveData 的生命周期优势,又避免了粘性事件的问题。

五、知识体系总览

下面这张图总结了 LiveData 的核心知识点和它们之间的关系。你可以把它当作一个快速索引,遇到问题的时候回来看看。

LiveData 知识体系总览 LiveData 核心 生命周期感知 自动清理观察者 数据一致性保证 变换操作 map:同步一对一 switchMap:异步一对多 MediatorLiveData 多数据源合并 动态添加/移除 事件总线用法 SingleLiveEvent SharedFlow 替代方案 LiveData 知识体系:设计思想 → 变换操作 → 多源合并 → 事件总线

这张图把 LiveData 的核心知识点分成了四个部分:设计思想、变换操作、多源合并和事件总线。每个部分之间都有联系,但各自独立。你在实际项目中,可以根据需求选择使用哪个部分。

好了,这一章的内容就到这里。LiveData 虽然看起来简单,但用好了能省很多事。下一章我们会聊聊 ViewModel 和 LiveData 的配合使用,到时候再深入讲讲它们之间的数据流设计。


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