3、LiveData:数据驱动的核心引擎
LiveData 这个东西,说白了就是 Android 官方给我们准备的一个「数据快递员」。它能把数据的变化,自动、及时地送到观察者手里。而且最妙的是——它只会在界面可见的时候才派送。我刚开始用的时候就觉得,这玩意儿简直是生命周期管理的救星。
3.1 LiveData 原理:它到底怎么工作的?
LiveData 的核心其实不复杂。它是一个被观察者模式的实现,但加上了生命周期感知能力。你想想看,传统的观察者模式,你得自己管理注册、注销,一不小心就内存泄漏。LiveData 帮你干了这些脏活累活。
它的内部维护了一个 SafeIterableMap,用来存储观察者。当数据变化时,它会遍历这个 Map,通知所有活跃的观察者。什么叫「活跃」?就是 LifecycleOwner 处于 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