我第一次接触 LiveData 的时候,觉得它非常惊艳。
在那个时代,Android 开发充满了 Callback、生命周期问题、内存泄漏风险。
LiveData 的出现,让 Android 开发第一次拥有了一个和生命周期结合的数据观察方案。
那时候我觉得:
LiveData 真的好棒,简单好用。
但是几年之后,我越来越少使用 LiveData。
现在的新项目里,我基本已经不会主动选择 LiveData,而更多使用 Kotlin Flow。
这是因为随着 Android 应用复杂度提升,LiveData 本身的一些设计限制越来越明显。
一、刚出道的 LiveData,为什么让我觉得惊艳?
在 LiveData 出现之前,Android 异步开发是什么样?
大量 Callback:
java
api.getUser(new Callback<User>() {
@Override
public void onSuccess(User user) {
textView.setText(user.name);
}
});
看起来简单,但是隐藏大量问题。
1. 生命周期问题
比如:
打开页面
↓
请求网络
↓
用户返回
↓
Activity销毁
↓
接口返回
↓
更新UI
↓
Crash
开发者需要自己判断:
java
if(!activity.isDestroyed()){
}
大量生命周期判断代码。
2. 数据和 UI 强绑定
以前:
请求成功
↓
Callback
↓
修改UI
业务代码和 UI 更新混在一起。
LiveData 出现:
kotlin
viewModel.user.observe(this){
name.text = it.name
}
突然变得非常自然。
它解决:
- 生命周期自动管理
- 自动取消观察
- 数据变化自动通知 UI
当时来看,这是非常大的进步。
二、LiveData 最大的价值:让 Android 有了生命周期感知的数据观察
LiveData 最大的设计:
kotlin
observe(owner){
}
它知道:
STARTED
RESUMED
DESTROYED
状态。
例如:
Activity:
onStart
↓
开始接收数据
onDestroy
↓
自动解绑
开发者不用再手动处理:
- 取消监听
- 防止内存泄漏
- 判断页面状态
所以 LiveData 的核心贡献:
它解决了 Android 世界中"数据观察"和"生命周期管理"的问题。
三、但是 LiveData 的第一个限制:它不是一个真正的数据流
这是 LiveData 最大的问题之一。
LiveData:
kotlin
val user = MutableLiveData<User>()
它更像:
一个可以被观察的数据容器。
它保存:
当前值
然后通知:
观察者
但是现代应用很多场景,本质是:
数据流。
例如搜索:
用户输入:
css
a
↓
ab
↓
abc
我们希望:
300ms没有输入
↓
请求abc
Flow:
kotlin
queryFlow
.debounce(300)
.flatMapLatest {
search(it)
}
非常自然。
因为 Flow 表达:
数据产生
↓
数据转换
↓
数据过滤
↓
数据消费
而 LiveData 更关注:
现在是什么值
四、LiveData 缺少完整的数据流处理能力
现代应用大量需要:
- map
- filter
- combine
- debounce
- retry
- catch
- flatMapLatest
Flow:
kotlin
userFlow
.filter {
}
.map {
}
.combine(otherFlow){
}
这些都是 Flow 的核心能力。
LiveData 虽然有:
kotlin
Transformations.map()
Transformations.switchMap()
但是能力有限。
复杂一点:
例如:
diff
用户信息
+
配置
+
网络状态
+
权限状态
LiveData 通常:
kotlin
MediatorLiveData
多个数据源手动管理。
代码容易越来越复杂。
五、LiveData 的第二个限制:它不适合作为 Data 层的数据类型
这是很多项目后期遇到的问题。
早期很多项目:
kotlin
class UserRepository {
fun getUser():LiveData<User>
}
当时觉得:
很方便。
但是问题来了:
LiveData 属于:
kotlin
androidx.lifecycle.LiveData
它本质是 Android 生命周期组件。
Data 层为什么不应该暴露 LiveData?
因为数据层关注:
数据怎么产生
数据怎么变化
而不是:
Activity有没有显示
Fragment有没有销毁
但是 LiveData 天生关注:
LifecycleOwner
例如:
STARTED
RESUMED
DESTROYED
这意味着:
数据层开始知道:
Android 生命周期模型。
举个例子:
数据库:
kotlin
@Query(
"select * from user"
)
fun observeUser():LiveData<User>
看起来很好。
但是如果:
后台 Service 需要用户数据:
或者:
Worker 需要监听数据:
它们怎么办?
因为它们并没有:
kotlin
LifecycleOwner
于是开始出现:
各种转换:
kotlin
asFlow()
其实:
如果数据源一开始就是 Flow:
这些问题不存在。
六、LiveData 会限制数据的消费方式
假设:
Repository:
kotlin
fun message():LiveData<Message>
UI:
没问题:
kotlin
observe()
但是:
后台任务:
kotlin
Coroutine
更希望:
kotlin
collect()
但是 LiveData:
设计目标就是:
Observer。
于是:
数据生产者决定了消费者模式。
而 Flow:
kotlin
fun message():Flow<Message>
消费者自己决定:
UI:
kotlin
collectAsState()
后台:
kotlin
collect()
测试:
kotlin
first()
同一个数据源:
不同消费方式。
七、LiveData 对事件处理一直比较尴尬
经典问题:
一次性事件。
例如:
登录成功:
kotlin
loginSuccess.value = true
页面:
kotlin
observe {
Toast.makeText(
"登录成功"
)
}
旋转屏幕。
LiveData 会重新发送:
arduino
true
结果:
Toast 又出现一次。
于是出现:
kotlin
SingleLiveEvent
或者:
kotlin
EventWrapper
但是这也说明:
LiveData 更适合:
状态。
不适合:
事件流。
事件:
点击一次
发送一次
消费一次
状态:
当前是什么
两者语义不同。
八、LiveData 的线程模型比较简单,也限制了能力
LiveData:
kotlin
setValue()
主线程。
后台:
kotlin
postValue()
看起来简单。
但是:
它隐藏了一些细节。
例如:
kotlin
postValue(1)
postValue(2)
postValue(3)
最终:
可能只收到:
3
原因:
LiveData 的目标:
保存最新状态。
它不是消息队列。
但是很多场景需要:
事件1
事件2
事件3
全部处理
例如:
- 下载进度
- 消息通知
- 操作事件
LiveData 并不适合。
九、LiveData 和 Kotlin 协程结合,不如 Flow 自然
现在 Android 主流:
css
Coroutine
+
Flow
但是 LiveData:
通常:
kotlin
viewModelScope.launch {
val result = repository.getUser()
liveData.value = result
}
中间:
python
Coroutine
↓
LiveData
↓
Observer
多了一层。
Flow:
kotlin
viewModelScope.launch {
repository.user()
.collect {
}
}
天然连接。
十、LiveData 最大的问题:简单很好,复杂容易失控
LiveData:
简单页面:
kotlin
val loading =
MutableLiveData<Boolean>()
非常舒服。
但是复杂页面:
多个 LiveData:
diff
UserLiveData
+
ConfigLiveData
+
NetworkLiveData
+
PermissionLiveData
最后:
vbnet
MediatorLiveData
Transformations
Event包装
各种工具类
越来越像:
自己实现一个数据流框架。
十一、LiveData 和 Flow,本质区别是什么?
LiveData:
一个生命周期感知的数据容器。
Flow:
一个可以被组合、转换、取消的数据流。
LiveData 思维:
数据变化了
通知我
Flow 思维:
数据本身就是流
我要处理这个流
总结
LiveData 没有错。
它是 Android 架构发展过程中非常重要的一环。
它解决:
生命周期安全的数据观察。
但是它也有明显限制:
-
它不是完整的数据流模型
-
数据组合能力有限
-
不适合作为 Data 层的数据类型
-
和协程结合不够自然
-
对事件处理存在天然缺陷
所以:
LiveData 适合 UI 层,但不应该成为整个应用的数据传递模型。