3、LiveData:数据驱动的核心引擎

LiveData 这个东西,说白了就是 Android 官方给我们准备的一个「数据快递员」。它能把数据的变化,自动、及时地送到观察者手里。而且最妙的是——它只会在界面可见的时候才派送。我刚开始用的时候就觉得,这玩意儿简直是生命周期管理的救星。

3.1 LiveData 原理:它到底怎么工作的?

LiveData 的核心其实不复杂。它是一个被观察者模式的实现,但加上了生命周期感知能力。你想想看,传统的观察者模式,你得自己管理注册、注销,一不小心就内存泄漏。LiveData 帮你干了这些脏活累活。

它的内部维护了一个 SafeIterableMap,用来存储观察者。当数据变化时,它会遍历这个 Map,通知所有活跃的观察者。什么叫「活跃」?就是 LifecycleOwner 处于 STARTED 或 RESUMED 状态。

核心机制:

  • 数据变化时,只通知「活跃」的观察者
  • 观察者自动跟随生命周期,不用手动解注册
  • 数据更新是粘性的——新观察者会立即收到当前值

嗯,这里要注意:粘性事件是个双刃剑。有时候你不想让新观察者收到旧数据,那就得自己处理一下。我在项目中遇到过这个问题,后面会讲怎么解决。

LiveData 工作原理 数据源 setValue / postValue 数据变化 LiveData SafeIterableMap 生命周期感知 观察者 A 活跃状态 ✓ 观察者 B 非活跃状态 ✗ 生命周期状态流转 DESTROYED CREATED STARTED RESUMED 只有 STARTED / RESUMED 状态的观察者才能收到数据更新

3.2 LiveData 使用:从入门到熟练

使用 LiveData 其实就三步:创建、观察、更新。我习惯在 ViewModel 里创建 LiveData,在 Activity/Fragment 里观察它。

// 第一步:在 ViewModel 中定义
class MyViewModel : ViewModel() {
    val userName: LiveData<String> = MutableLiveData()
}

// 第二步:在 Activity 中观察
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val viewModel: MyViewModel by viewModels()
        viewModel.userName.observe(this) { name ->
            // 数据变化时自动更新 UI
            textView.text = name
        }
    }
}

你看,就这么简单。但有个细节我得提醒你——observe() 的第一个参数是 LifecycleOwner。传错了或者传了 null,观察者就收不到通知了。我曾经踩过这个坑,传了一个 Fragment 的 View 作为 LifecycleOwner,结果数据死活不更新。

我的习惯:在 Fragment 中观察 LiveData 时,永远传 viewLifecycleOwner,而不是 this。这样可以避免 Fragment 视图销毁后还收到回调。

3.3 MutableLiveData:能读能写才是真

LiveData 本身只暴露了 observe()getValue() 方法,数据只能读不能写。那数据从哪来?答案是 MutableLiveData。

MutableLiveData 继承自 LiveData,额外提供了 setValue()postValue() 两个方法。区别很简单:

方法 调用线程 特点
setValue() 主线程 立即更新,同步操作
postValue() 任意线程 异步更新,内部切到主线程

我个人习惯在 ViewModel 内部用 MutableLiveData,对外暴露成 LiveData。这样外部只能观察,不能篡改数据,保证了数据流向的单向性。

class UserViewModel : ViewModel() {
    // 对外暴露不可变的 LiveData
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> get() = _user

    fun loadUser(userId: String) {
        viewModelScope.launch {
            val result = repository.fetchUser(userId)
            _user.value = result  // 内部更新
        }
    }
}

注意:postValue() 有个小坑——如果你连续调用多次 postValue(),只有最后一次的值会生效。因为它是通过 Handler 异步执行的,中间的值会被覆盖。我遇到过这个问题,排查了半天才发现是 postValue 的「去重」机制导致的。

3.4 Transformations.map:数据转换的利器

有时候你不想直接暴露原始数据,而是想对数据做一层转换。比如你有一个 User 对象,但 UI 只需要显示用户名。这时候 Transformations.map 就派上用场了。

class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()

    // 自动将 User 转换为用户名
    val userName: LiveData<String> = Transformations.map(_user) { user ->
        "${user.firstName} ${user.lastName}"
    }
}

这个 map 是惰性求值的——只有当你观察 userName 时,转换逻辑才会执行。而且它是响应式的,只要 _user 变了,userName 会自动重新计算并通知观察者。

嗯,这里有个细节:Transformations.map 是在主线程执行的。如果你的转换逻辑很重(比如解析 JSON),记得用 switchMap 配合协程来处理。

3.5 MediatorLiveData:合并多个数据源

MediatorLiveData 是 LiveData 的「增强版」。它可以同时观察多个 LiveData 源,任何一个源变化了,它都会收到通知。我经常用它来做数据合并的场景。

class WeatherViewModel : ViewModel() {
    private val temperature = MutableLiveData<Int>()
    private val humidity = MutableLiveData<Int>()

    val weatherInfo = MediatorLiveData<String>().apply {
        // 添加两个数据源
        addSource(temperature) { temp ->
            val hum = humidity.value ?: 0
            value = "温度: ${temp}°C, 湿度: ${hum}%"
        }
        addSource(humidity) { hum ->
            val temp = temperature.value ?: 0
            value = "温度: ${temp}°C, 湿度: ${hum}%"
        }
    }
}

你看,MediatorLiveData 就像一个「数据汇流排」。它把多个数据流合并成一个,UI 只需要观察这一个 LiveData 就够了。我在做天气 App 时就用这个模式,省了不少代码。

使用场景总结:

  • 需要合并多个数据源时
  • 需要监听另一个 LiveData 的变化并做响应
  • 需要动态添加/移除数据源

3.6 实战:数据驱动 UI

说了这么多理论,咱们来点实际的。假设我们要做一个用户详情页,需要展示用户信息、加载状态和错误信息。用 LiveData 来实现数据驱动 UI,代码会非常清晰。

// 定义 UI 状态
data class UserUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

class UserDetailViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UserUiState>()
    val uiState: LiveData<UserUiState> get() = _uiState

    fun loadUser(userId: String) {
        _uiState.value = UserUiState(isLoading = true)
        viewModelScope.launch {
            try {
                val user = repository.getUser(userId)
                _uiState.value = UserUiState(user = user)
            } catch (e: Exception) {
                _uiState.value = UserUiState(error = e.message)
            }
        }
    }
}

// Activity 中观察
class UserDetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val viewModel: UserDetailViewModel by viewModels()
        viewModel.uiState.observe(this) { state ->
            // 根据状态更新 UI
            progressBar.isVisible = state.isLoading
            errorText.isVisible = state.error != null
            errorText.text = state.error
            userInfoGroup.isVisible = state.user != null
            state.user?.let { bindUser(it) }
        }
    }
}

这个模式我用了好几年,真心推荐。它把 UI 的状态抽象成一个不可变的数据类,ViewModel 只负责生产状态,UI 只负责消费状态。数据流向是单向的,调试起来特别方便。

避坑指南:我曾经在项目中把 LiveData 的观察逻辑写在 Fragment 的 onViewCreated 里,结果每次重建视图都会重新注册观察者,导致回调重复执行。后来我改成在 onCreate 里观察,或者用 viewLifecycleOwner,问题就解决了。

LiveData 虽然简单,但用好了能解决很多实际问题。它不像 RxJava 那样功能强大,但胜在轻量、易用、与生命周期完美结合。说白了,它就是为 Android 量身定做的数据驱动工具。


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