LiveData的基本使用
1. LiveData 是什么?
想象 LiveData 是一个"智能快递箱":
- 快递箱可以存放物品(数据)
- 只有在收件人在家时(Activity/Fragment 活跃)才会通知
- 收件人搬家(配置更改)后仍能收到快递(数据)
// 创建 LiveData
private val _score = MutableLiveData<Int>()
val score: LiveData<Int> = _score
// 更新数据(投递快递)
_score.value = 100
// 观察数据(等待收货)
score.observe(viewLifecycleOwner) { newScore ->
// 处理新的分数
updateUI(newScore)
}
2. LiveData 工作流程
graph TD
A[LiveData] --> B[存储数据]
A --> C[注册观察者]
A --> D[分发数据]
B --> E[setValue/postValue]
C --> F[observe]
D --> G[活跃状态分发]
D --> H[非活跃状态不分发]
活跃状态分发和非活跃状态分发
让我详细解释 LiveData 的活跃状态和非活跃状态:
1. 什么是活跃状态?
// 活跃状态指的是:
// 1. Activity/Fragment 处于 STARTED 或 RESUMED 状态
// 2. 对应生命周期:onStart() 之后,onPause() 之前
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.data.observe(viewLifecycleOwner) { data ->
// 只有在 Fragment 可见(活跃)时才会收到更新
updateUI(data)
}
}
}
2. 生命周期状态图
graph TD
A[Fragment生命周期] --> B[onCreate]
B --> C[onStart]
C --> D[onResume]
D --> E[onPause]
E --> F[onStop]
C --> G[活跃状态开始]
D --> G
E --> H[活跃状态结束]
F --> H
3. 数据分发机制
class ExampleViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun updateData() {
// 场景1:Fragment处于活跃状态
_data.value = "新数据" // Observer 立即收到更新
// 场景2:Fragment处于非活跃状态
_data.value = "新数据" // 数据更新被保存
// 当 Fragment 重新变为活跃状态时,Observer 会收到最新值
}
}
4. 具体示例
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.data.observe(this) { data ->
// 1. Activity 可见时(活跃状态)
// - 立即收到当前值
// - 收到后续更新
// 2. Activity 在后台时(非活跃状态)
// - 不会收到更新
// - 更新会被保存
// - Activity 重新可见时收到最新值
}
}
}
5. 实际应用场景
class ChatViewModel : ViewModel() {
private val _messages = MutableLiveData<List<Message>>()
val messages: LiveData<List<Message>> = _messages
fun sendMessage(message: Message) {
viewModelScope.launch {
// 1. 用户在聊天界面(活跃状态)
// - 消息立即显示
_messages.value = currentMessages + message
// 2. 用户切到后台(非活跃状态)
// - 消息更新被保存
// - 用户回到应用时看到最新消息
}
}
}
6. 活跃状态的优势
- 避免内存泄漏:
// LiveData 自动处理生命周期,不会泄漏
viewModel.data.observe(viewLifecycleOwner) { /* ... */ }
- 避免崩溃:
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
fun updateUI() {
// 在非活跃状态下更新数据是安全的
// 不会导致崩溃或异常
_data.value = "新数据"
}
}
- 确保数据一致性:
// 配置更改时(如旋转屏幕)
// 1. Activity/Fragment 重建
// 2. 重新变为活跃状态
// 3. 自动收到最新数据
viewModel.data.observe(viewLifecycleOwner) { data ->
// 总是显示最新数据
updateUI(data)
}
总结:
-
活跃状态:
- STARTED 或 RESUMED 生命周期状态
- 立即接收数据更新
- 确保 UI 与数据同步
-
非活跃状态:
- CREATED 或 DESTROYED 生命周期状态
- 不接收数据更新
- 更新被保存等待重新活跃
-
优势:
- 内存安全
- 生命周期感知
- 数据一致性
- 配置更改处理
-
使用建议:
- 始终使用正确的 LifecycleOwner
- 在合适的生命周期阶段观察
- 利用自动数据保存机制
这种机制确保了数据更新的安全性和及时性,同时避免了常见的内存和生命周期问题。
3. 实际使用示例
class GameViewModel : ViewModel() {
// 1. 私有的可变 LiveData
private val _score = MutableLiveData<Int>()
// 2. 公开的不可变 LiveData
val score: LiveData<Int> = _score
// 更新分数
fun updateScore(newScore: Int) {
// 主线程更新
_score.value = newScore
// 或在后台线程更新
_score.postValue(newScore)
}
}
class GameFragment : Fragment() {
private val viewModel: GameViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 观察分数变化
viewModel.score.observe(viewLifecycleOwner) { newScore ->
scoreText.text = "分数:$newScore"
}
}
}
4. LiveData 的生命周期感知
graph LR
A[Activity/Fragment] --> B[STARTED]
A --> C[RESUMED]
A --> D[PAUSED]
A --> E[STOPPED]
B --> F[LiveData活跃]
C --> F
D --> G[LiveData非活跃]
E --> G
5. 高级用法示例
class WeatherViewModel : ViewModel() {
// 1. 转换 LiveData
private val _temperature = MutableLiveData<Float>()
val temperatureString = Transformations.map(_temperature) { temp ->
String.format("%.1f°C", temp)
}
// 2. 合并多个 LiveData
private val _humidity = MutableLiveData<Int>()
val weatherInfo = MediatorLiveData<String>().apply {
addSource(_temperature) { temp ->
value = "温度: $temp°C, 湿度: ${_humidity.value}%"
}
addSource(_humidity) { humidity ->
value = "温度: ${_temperature.value}°C, 湿度: $humidity%"
}
}
}
代码讲解
1. LiveData 转换 (Transformations.map)
想象你有一个温度计(_temperature
):
// 1. 温度计显示的是数字 (比如 26.5)
private val _temperature = MutableLiveData<Float>()
// 2. 我们需要把数字转成更友好的显示格式 (比如 "26.5°C")
val temperatureString = Transformations.map(_temperature) { temp ->
String.format("%.1f°C", temp)
}
这就像:
- 温度计显示:26.5
- 自动转换成:"26.5°C"
生活中的例子:
- 就像一个翻译官,把数字自动转换成人类易读的格式
- 或者像一个售货机,投入硬币(数字),自动出来商品(格式化字符串)
2. 合并多个 LiveData (MediatorLiveData)
想象你有一个气象站,需要同时显示温度和湿度:
// 1. 温度传感器
private val _temperature = MutableLiveData<Float>()
// 2. 湿度传感器
private val _humidity = MutableLiveData<Int>()
// 3. 综合显示屏
val weatherInfo = MediatorLiveData<String>().apply {
// 当温度变化时更新显示屏
addSource(_temperature) { temp ->
value = "温度: $temp°C, 湿度: ${_humidity.value}%"
}
// 当湿度变化时更新显示屏
addSource(_humidity) { humidity ->
value = "温度: ${_temperature.value}°C, 湿度: $humidity%"
}
}
这就像:
- 你有两个传感器(温度和湿度)
- 有一个大显示屏(
weatherInfo
) - 任何一个传感器数据变化,显示屏都会更新全部信息
生活中的例子:
- 就像一个天气播报员,同时关注温度计和湿度计
- 任何一个数据变化,都会重新播报完整的天气信息
使用示例
class WeatherActivity : AppCompatActivity() {
private val viewModel: WeatherViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 观察格式化后的温度
viewModel.temperatureString.observe(this) { formattedTemp ->
// 显示: "26.5°C"
tempTextView.text = formattedTemp
}
// 2. 观察完整天气信息
viewModel.weatherInfo.observe(this) { info ->
// 显示: "温度: 26.5°C, 湿度: 60%"
weatherInfoTextView.text = info
}
}
}
流程图
graph TD
A[温度传感器] --> B[数据转换]
B --> C[格式化温度显示]
D[温度传感器] --> E[综合显示屏]
F[湿度传感器] --> E
E --> G[完整天气信息]
总结:
-
Transformations.map:
- 输入:原始数据(如:26.5)
- 输出:转换后的数据(如:"26.5°C")
- 就像一个自动转换器
-
MediatorLiveData:
- 输入:多个数据源(温度、湿度)
- 输出:组合后的信息
- 就像一个信息集成器
这样理解是不是清晰多了?它就像一个智能气象站,可以自动处理和显示各种天气数据。
6. LiveData 的底层实现原理
// 简化的 LiveData 实现原理
class SimpleLiveData<T> {
// 存储数据
private var mData: T? = null
// 存储观察者
private val observers = mutableListOf<Observer<T>>()
// 设置数据
fun setValue(value: T) {
mData = value
dispatchValue()
}
// 注册观察者
fun observe(owner: LifecycleOwner, observer: Observer<T>) {
// 1. 将观察者和生命周期绑定
val wrapper = LifecycleBoundObserver(owner, observer)
observers.add(wrapper)
// 2. 如果处于活跃状态,立即发送数据
if (owner.lifecycle.currentState.isAtLeast(STARTED)) {
observer.onChanged(mData)
}
}
// 分发数据
private fun dispatchValue() {
observers.forEach { observer ->
if (observer.isActive) {
observer.onChanged(mData)
}
}
}
}
7. 常见使用场景
// 1. 网络请求结果
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
fun fetchUser() {
viewModelScope.launch {
try {
val result = userRepository.getUser()
_user.value = result
} catch (e: Exception) {
// 处理错误
}
}
}
}
// 2. UI 状态管理
sealed class UiState {
object Loading : UiState()
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()
}
class MainViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState>()
val uiState: LiveData<UiState> = _uiState
fun loadData() {
_uiState.value = UiState.Loading
viewModelScope.launch {
try {
val result = repository.getData()
_uiState.value = UiState.Success(result)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
}
8. 最佳实践
- 数据封装:
// ViewModel 中
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data // 只暴露不可变的 LiveData
- 生命周期绑定:
// 在 Fragment 中
viewModel.data.observe(viewLifecycleOwner) { data ->
// 使用 viewLifecycleOwner 而不是 this
}
- 状态管理:
sealed class Result<out T> {
object Loading : Result<Nothing>()
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
总结:
-
核心特点:
- 生命周期感知
- 数据持有
- 自动分发
-
使用建议:
- 正确封装数据
- 使用适当的生命周期所有者
- 合理处理配置变更
-
优势:
- 避免内存泄漏
- 确保 UI 与数据同步
- 处理配置变更
-
注意事项:
- 主线程更新使用 value
- 后台线程更新使用 postValue
- 注意生命周期绑定
LiveData 是 Android 架构组件中非常重要的一部分,合理使用可以大大简化应用开发。
LiveData的使用问题
让我详细介绍 LiveData 在开发中的注意事项:
1. 内存泄漏问题
// ❌ 错误示例:使用 Activity 作为生命周期所有者
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.data.observe(this) { // 使用 Fragment 自身
updateUI(it)
}
}
}
// ✅ 正确示例:使用 viewLifecycleOwner
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.data.observe(viewLifecycleOwner) { // 使用 View 的生命周期
updateUI(it)
}
}
}
viewLifecycleOwner的使用
1. Fragment 生命周期 vs View 生命周期
想象 Fragment 像一个房子,而 View 像房子里的家具:
- Fragment (房子) 可能会保留,但内部装修(View)可能会更换
- 当屏幕旋转时,View(家具)会被销毁重建,但 Fragment(房子)还在
class MyFragment : Fragment() {
// ❌ 错误:使用 Fragment 的生命周期
// 相当于把家具绑定到房子上,即使装修时也不放手
viewModel.data.observe(this) {
updateUI(it) // 可能会操作已经不存在的View
}
// ✅ 正确:使用 View 的生命周期
// 相当于把家具绑定到装修周期,重新装修时自动解绑
viewModel.data.observe(viewLifecycleOwner) {
updateUI(it) // 安全地操作当前存在的View
}
}
2. 生命周期示意图
graph TD
A[Fragment生命周期] --> B[onCreate]
B --> C[onCreateView]
C --> D[onViewCreated]
D --> E[onDestroyView]
E --> F[onDestroy]
G[View生命周期] --> H[onCreateView时创建]
H --> I[onDestroyView时销毁]
I --> J[配置更改时重建]
3. 实际场景举例
class ProfileFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 场景:用户旋转屏幕
viewModel.userData.observe(viewLifecycleOwner) { user ->
// 1. 旧的View被销毁时,观察者自动解绑
// 2. 新的View创建时,重新绑定观察者
// 3. 避免操作已销毁的View
nameTextView.text = user.name // 安全操作
}
}
}
4. 为什么会内存泄漏?
如果使用 Fragment 本身作为生命周期所有者:
- Fragment 重建时,旧的观察者没有被移除
- 新的观察者被添加
- 导致观察者不断累积
- 最终造成内存泄漏
就像:
- 每次装修都请了新的装修工
- 但旧的装修工一直没解雇
- 最终导致工资支出越来越多(内存泄漏)
5. 正确使用的好处
// ✅ 使用 viewLifecycleOwner 的优势
viewModel.data.observe(viewLifecycleOwner) { data ->
// 1. View 销毁时自动解绑观察者
// 2. 避免操作已销毁的 View
// 3. 防止内存泄漏
// 4. 确保 UI 操作的安全性
}
6. viewLifecycleOwner的使用及创建
让我详细解释 viewLifecycleOwner
的来源和工作原理:
1. viewLifecycleOwner 的创建过程
// Fragment 内部实现(简化版)
class Fragment {
// 1. Fragment 内部持有 viewLifecycleOwner
private var _viewLifecycleOwner: LifecycleOwner? = null
// 2. 对外暴露的只读属性
val viewLifecycleOwner: LifecycleOwner
get() = _viewLifecycleOwner ?: error("只能在 onCreateView 之后访问")
// 3. 在 onCreateView 中创建
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 创建 View 的同时创建 LifecycleOwner
_viewLifecycleOwner = FragmentViewLifecycleOwner()
return null
}
// 4. 在 onDestroyView 中清除
override fun onDestroyView() {
super.onDestroyView()
_viewLifecycleOwner = null
}
}
2. 生命周期流程图
graph TD
A[Fragment创建] --> B[onCreateView]
B --> C[创建viewLifecycleOwner]
C --> D[onViewCreated]
D --> E[View可以使用]
E --> F[onDestroyView]
F --> G[清除viewLifecycleOwner]
3. 实际工作原理
// FragmentViewLifecycleOwner 的简化实现
class FragmentViewLifecycleOwner : LifecycleOwner {
private val lifecycle = LifecycleRegistry(this)
override fun getLifecycle(): Lifecycle = lifecycle
// Fragment 内部会调用这些方法
fun handleLifecycleEvent(event: Lifecycle.Event) {
lifecycle.handleLifecycleEvent(event)
}
}
class Fragment {
fun performCreateView() {
// View 创建时
_viewLifecycleOwner?.handleLifecycleEvent(ON_CREATE)
}
fun performViewCreated() {
// View 创建完成时
_viewLifecycleOwner?.handleLifecycleEvent(ON_START)
}
fun performDestroyView() {
// View 销毁时
_viewLifecycleOwner?.handleLifecycleEvent(ON_DESTROY)
}
}
4. 使用示例
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// viewLifecycleOwner 的生命周期:
// 1. 创建:onCreateView 时
// 2. 活跃:onViewCreated 后
// 3. 销毁:onDestroyView 时
viewModel.data.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
}
}
viewLifecycleOwner总结
-
创建者:
- Fragment 内部创建
- 在 onCreateView 时创建
- 在 onDestroyView 时销毁
-
持有者:
- Fragment 持有 viewLifecycleOwner
- 通过私有变量 _viewLifecycleOwner 存储
- 通过公开属性 viewLifecycleOwner 访问
-
生命周期:
- 创建:onCreateView
- 活跃:onViewCreated
- 销毁:onDestroyView
-
使用原因:
- 精确跟踪 View 的生命周期
- 避免内存泄漏
- 确保 UI 操作安全
记住:
- Fragment 是房子的主人(创建者和持有者)
- viewLifecycleOwner 是房子里的家具的管理者
- 当房子装修(View创建)时,管理者上任
- 当房子重新装修(View销毁)时,管理者离职
总结
-
为什么使用 viewLifecycleOwner:
- 自动处理 View 的生命周期
- 防止内存泄漏
- 避免空指针异常
- 确保 UI 操作安全
-
生命周期特点:
- Fragment 生命周期长于 View
- View 会在配置更改时重建
- viewLifecycleOwner 跟随 View 生命周期
-
最佳实践:
- 在 Fragment 中操作 View 时总是使用 viewLifecycleOwner
- 只在确实需要 Fragment 生命周期时才使用 this
记住:
- Fragment 是房子(可能长期存在)
- View 是家具(可能经常更换)
- viewLifecycleOwner 确保我们只操作当前的家具,不会去动已经扔掉的旧家具
2. 粘性事件问题
class EventViewModel : ViewModel() {
// ❌ 错误示例:直接使用 LiveData 处理一次性事件
private val _navigateToDetail = MutableLiveData<Boolean>()
val navigateToDetail: LiveData<Boolean> = _navigateToDetail
// ✅ 正确示例:使用 Event 包装器
private val _event = MutableLiveData<Event<String>>()
val event: LiveData<Event<String>> = _event
// Event 包装类
class Event<T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
}
}
// 使用
viewModel.event.observe(viewLifecycleOwner) { event ->
event.getContentIfNotHandled()?.let { content ->
// 处理事件
}
}
粘性事件详解
1. 什么是粘性事件?
想象你有一个便利贴(LiveData):
- 普通便利贴:贴一次,看一次就扔掉
- 粘性便利贴:贴在那里,每个路过的人都会看到
2. 实际场景举例
// 场景:点击列表项跳转到详情页
class ListViewModel : ViewModel() {
// ❌ 粘性事件问题
private val _navigate = MutableLiveData<Boolean>()
fun onItemClick() {
_navigate.value = true // 贴上便利贴"需要跳转"
}
}
// 使用时的问题
class ListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.navigate.observe(viewLifecycleOwner) { shouldNavigate ->
if (shouldNavigate) {
// 问题:
// 1. 屏幕旋转后,Fragment 重建
// 2. 重新订阅,又会收到之前的 true
// 3. 导致重复跳转!
navigateToDetail()
}
}
}
}
这就像:
- 你贴了一张"该吃午饭了"的便利贴
- 中午看到便利贴去吃了午饭
- 下午回来,便利贴还在
- 看到便利贴又去吃了一次午饭!
3. 解决方案:Event 包装器
// Event 包装器就像一次性便利贴
class Event<T>(private val content: T) {
private var hasBeenHandled = false // 是否已经被查看过
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
// 已经看过了,返回 null
null
} else {
// 第一次看,标记为已读,返回内容
hasBeenHandled = true
content
}
}
}
// 使用 Event 包装器
class ListViewModel : ViewModel() {
private val _navigationEvent = MutableLiveData<Event<Unit>>()
fun onItemClick() {
// 发送一次性事件
_navigationEvent.value = Event(Unit)
}
}
// 观察事件
viewModel.navigationEvent.observe(viewLifecycleOwner) { event ->
// 只有未处理的事件才会被处理
event.getContentIfNotHandled()?.let {
// 跳转到详情页(只会执行一次)
navigateToDetail()
}
}
这就像:
- 使用带"已读"标记的便利贴
- 第一次看到便利贴,执行操作并标记"已读"
- 之后再看到这个便利贴,发现"已读"就忽略它
4. 流程图
graph TD
A[普通 LiveData] --> B[粘性事件问题]
B --> C[配置更改后重复触发]
D[Event 包装器] --> E[一次性事件]
E --> F[只触发一次]
F --> G[避免重复处理]
5. 实际应用场景
class LoginViewModel : ViewModel() {
private val _loginSuccess = MutableLiveData<Event<String>>()
val loginSuccess: LiveData<Event<String>> = _loginSuccess
fun login(username: String, password: String) {
viewModelScope.launch {
try {
val token = loginRepository.login(username, password)
// 发送一次性登录成功事件
_loginSuccess.value = Event("登录成功!")
} catch (e: Exception) {
// 处理错误
}
}
}
}
// 使用
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.loginSuccess.observe(viewLifecycleOwner) { event ->
event.getContentIfNotHandled()?.let { message ->
// 显示成功提示(只显示一次)
showToast(message)
// 跳转到主页(只跳转一次)
navigateToMain()
}
}
}
}
总结:
-
粘性事件问题:
- LiveData 保留最后一个值
- 新观察者会收到旧数据
- 导致事件重复触发
-
Event 包装器解决方案:
- 为事件添加"已读"标记
- 确保每个事件只处理一次
- 避免重复触发
-
适用场景:
- 导航事件
- 提示消息
- 一次性操作
- 状态变化通知
记住:
- 普通 LiveData 像可重复使用的便利贴
- Event 包装器像一次性便利贴
- 使用 Event 可以避免事件重复触发的问题
3. 线程安全问题
class DataViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
fun updateData() {
viewModelScope.launch(Dispatchers.IO) {
// ❌ 错误示例:在后台线程直接使用 value
// _data.value = "新数据"
// ✅ 正确示例:在后台线程使用 postValue
_data.postValue("新数据")
// ✅ 或者切换到主线程
withContext(Dispatchers.Main) {
_data.value = "新数据"
}
}
}
}
4. 数据倒灌问题
// ❌ 问题示例
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 配置变更后会重新触发,导致数据倒灌
viewModel.showDialog.observe(this) {
showDialog()
}
}
}
// ✅ 解决方案:使用 SingleLiveEvent 或 Event 包装器
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
}
数据倒灌详解
让我用生动的比喻来解释数据倒灌问题:
1. 什么是数据倒灌?
想象你有一个自动售货机(LiveData):
- 正常情况:投币(发送事件)→ 出货(显示对话框)
- 倒灌情况:机器重启后(屏幕旋转),没投币也出货了(自动显示对话框)
2. 具体场景举例
// 场景:点击按钮显示对话框
class MainViewModel : ViewModel() {
// 对话框显示状态
private val _showDialog = MutableLiveData<Boolean>()
val showDialog: LiveData<Boolean> = _showDialog
fun onButtonClick() {
// 点击按钮,显示对话框
_showDialog.value = true
}
}
发生的问题:
- 用户点击按钮,显示对话框
- 用户旋转屏幕
- Activity 重建
- 对话框又自动显示了一次!
这就像:
- 你买了一瓶水(显示对话框)
- 转身的时候售货机重启了(屏幕旋转)
- 重启后,售货机又自动给了你一瓶水(对话框又显示)
3. 解决方案图解
graph TD
A[普通 LiveData] --> B[存储上一次值]
B --> C[新观察者到来]
C --> D[重新发送上一次值]
D --> E[导致倒灌]
F[SingleLiveEvent] --> G[使用标记位]
G --> H[新观察者到来]
H --> I[检查标记位]
I --> J[避免倒灌]
4. SingleLiveEvent 解决方案
class MainViewModel : ViewModel() {
// 使用 SingleLiveEvent 替代普通 LiveData
private val _showDialog = SingleLiveEvent<Boolean>()
val showDialog: LiveData<Boolean> = _showDialog
fun onButtonClick() {
_showDialog.value = true // 只会触发一次
}
}
这就像:
- 给售货机加了一个"一次性标记"
- 每次出货后标记会自动清除
- 重启后,看到标记已清除,就不会重复出货
5. 实际工作原理
class SingleLiveEvent<T> : MutableLiveData<T>() {
// AtomicBoolean 就像一个带锁的开关
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { t ->
// compareAndSet 就像"查看并翻转开关":
// 1. 如果开关是开的(true),就关上(false)并返回true
// 2. 如果开关是关的(false),就保持关闭并返回false
if (pending.compareAndSet(true, false)) {
observer.onChanged(t) // 只有开关是开的才执行
}
}
}
override fun setValue(t: T?) {
pending.set(true) // 设置值时打开开关
super.setValue(t)
}
}
6. 使用场景
// 1. 显示对话框
val showDialog = SingleLiveEvent<Unit>()
// 2. 显示 Toast
val showToast = SingleLiveEvent<String>()
// 3. 页面导航
val navigation = SingleLiveEvent<String>()
// 使用示例
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 不会重复触发
viewModel.showDialog.observe(this) {
AlertDialog.Builder(this)
.setMessage("这个对话框不会重复显示")
.show()
}
}
}
总结:
-
数据倒灌问题:
- 配置更改时 Activity/Fragment 重建
- LiveData 保留最后一个值
- 新观察者会收到旧数据
- 导致重复触发
-
SingleLiveEvent 解决方案:
- 使用标记位控制事件分发
- 确保每个事件只触发一次
- 避免配置更改时重复触发
-
适用场景:
- 显示对话框
- Toast 提示
- 页面导航
- 一次性事件
记住:
- 普通 LiveData 像普通售货机,重启后可能重复出货
- SingleLiveEvent 像智能售货机,有"一次性标记",避免重复出货
5. 状态管理
// ✅ 推荐:使用密封类管理状态
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
class MainViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState<Data>>()
val uiState: LiveData<UiState<Data>> = _uiState
fun loadData() {
_uiState.value = UiState.Loading
viewModelScope.launch {
try {
val result = repository.getData()
_uiState.value = UiState.Success(result)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
}
6. 数据转换
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
// ✅ 推荐:使用 map 转换数据
val userName = Transformations.map(_user) { user ->
"${user.firstName} ${user.lastName}"
}
// ✅ 推荐:使用 switchMap 处理数据源切换
val userPosts = Transformations.switchMap(_user) { user ->
repository.getPostsForUser(user.id)
}
}
7. 多个数据源合并
class WeatherViewModel : ViewModel() {
private val _temperature = MutableLiveData<Float>()
private val _humidity = MutableLiveData<Int>()
// ✅ 推荐:使用 MediatorLiveData 合并多个数据源
val weatherInfo = MediatorLiveData<String>().apply {
fun update() {
val temp = _temperature.value
val humidity = _humidity.value
if (temp != null && humidity != null) {
value = "温度: $temp°C, 湿度: $humidity%"
}
}
addSource(_temperature) { update() }
addSource(_humidity) { update() }
}
}
8. 生命周期问题
// ❌ 错误示例:在错误的生命周期阶段观察
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 不要在这里观察 LiveData
viewModel.data.observe(viewLifecycleOwner) { }
}
}
// ✅ 正确示例
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 在这里观察 LiveData
viewModel.data.observe(viewLifecycleOwner) { }
}
}
LiveData生命周期问题
1. Fragment 生命周期顺序
想象开一家店铺的过程:
onCreate
: 注册营业执照(Fragment 被创建)onCreateView
: 装修店铺(创建布局)onViewCreated
: 店铺装修完成,可以开始营业(View 已创建完成)
graph TD
A[Fragment 创建] --> B[onCreate: 初始化基础配置]
B --> C[onCreateView: 创建View]
C --> D[onViewCreated: View创建完成]
D --> E[可以安全操作View]
2. 为什么不能在 onCreate 中观察?
// ❌ 错误示例
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.data.observe(viewLifecycleOwner) { data ->
// 问题:这时 View 还没创建!
textView.text = data // 可能会崩溃
}
}
这就像:
- 你在店铺还没装修好时
- 就想摆放商品
- 但架子还没安装,当然会出问题!
3. 为什么要在 onViewCreated 中观察?
// ✅ 正确示例
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.data.observe(viewLifecycleOwner) { data ->
// 这时 View 已经创建好了
textView.text = data // 安全!
}
}
这就像:
- 等店铺装修完成
- 架子、柜台都安装好了
- 这时候摆放商品就很安全
4. 实际应用示例
class ProductFragment : Fragment() {
private val viewModel: ProductViewModel by viewModels()
// ❌ 错误:onCreate 太早了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// View 还不存在
}
// ❌ 错误:onCreateView 还在创建 View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_product, container, false)
// View 正在创建中
}
// ✅ 正确:onViewCreated View 已创建完成
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 1. View 已经创建好了
// 2. 可以安全地设置监听器
// 3. 可以安全地更新 UI
viewModel.products.observe(viewLifecycleOwner) { products ->
adapter.submitList(products)
}
}
}
总结:
-
时机问题:
onCreate
: 太早,View 还没创建onCreateView
: 正在创建 ViewonViewCreated
: 刚好,View 已创建完成
-
安全性:
- 在
onViewCreated
中可以安全操作 View - 避免空指针异常
- 确保 UI 更新有效
- 在
-
最佳实践:
- LiveData 观察者设置在
onViewCreated
- UI 相关操作都在
onViewCreated
- 保证 View 操作的安全性
- LiveData 观察者设置在
记住:
onCreate
就像注册公司onCreateView
就像装修店铺onViewCreated
就像装修完成,可以开始营业了- 要在店铺装修好(View 创建完成)后才能开始摆放商品(更新 UI)
9. 最佳实践总结
graph TD
A[LiveData最佳实践] --> B[生命周期处理]
A --> C[线程安全]
A --> D[事件处理]
A --> E[状态管理]
B --> B1[使用viewLifecycleOwner]
B --> B2[正确的观察时机]
C --> C1[主线程用value]
C --> C2[后台线程用postValue]
D --> D1[使用Event包装器]
D --> D2[使用SingleLiveEvent]
E --> E1[使用密封类]
E --> E2[合理的状态转换]
关键注意点:
-
生命周期绑定:
- 使用正确的 LifecycleOwner
- 在合适的生命周期阶段观察
-
线程安全:
- 主线程使用 value
- 后台线程使用 postValue
-
事件处理:
- 使用 Event 包装器
- 或使用 SingleLiveEvent
-
状态管理:
- 使用密封类
- 合理处理加载状态
-
数据转换:
- 使用 Transformations
- 使用 MediatorLiveData
遵循这些最佳实践,可以避免常见问题,提高代码质量和可维护性。
LiveData 和 stateFlow的使用对比
1. 基本概念对比
想象两种不同的广播系统:
- LiveData 像是电视广播:观众打开电视才能收到节目,关掉就不会收到
- StateFlow 像是电台广播:一直在播放,即使没人收听也会持续广播
2. 代码对比
class WeatherViewModel : ViewModel() {
// LiveData 方式
private val _weatherLiveData = MutableLiveData<String>()
val weatherLiveData: LiveData<String> = _weatherLiveData
// StateFlow 方式
private val _weatherFlow = MutableStateFlow<String>("晴天")
val weatherFlow: StateFlow<String> = _weatherFlow
fun updateWeather(weather: String) {
// LiveData 更新
_weatherLiveData.value = weather
// StateFlow 更新
_weatherFlow.value = weather
}
}
3. 主要区别
graph TD
A[主要区别] --> B[初始值]
A --> C[生命周期感知]
A --> D[热流/冷流]
A --> E[空值处理]
B --> B1[LiveData: 可空]
B --> B2[StateFlow: 必须有初始值]
C --> C1[LiveData: 自动感知]
C --> C2[StateFlow: 需手动处理]
D --> D1[LiveData: 冷流]
D --> D2[StateFlow: 热流]
E --> E1[LiveData: 允许null]
E --> E2[StateFlow: 不建议null]
4. 使用场景对比
class WeatherFragment : Fragment() {
private val viewModel: WeatherViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 1. LiveData 观察方式(像看电视)
viewModel.weatherLiveData.observe(viewLifecycleOwner) { weather ->
updateUI(weather)
}
// 2. StateFlow 观察方式(像听广播)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.weatherFlow
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { weather ->
updateUI(weather)
}
}
}
}
5. 特点对比
LiveData(电视广播):
// 1. 不需要初始值
private val _channel = MutableLiveData<String>()
// 2. 自动处理生命周期
viewModel.channel.observe(viewLifecycleOwner) {
// 只在 Activity/Fragment 活跃时接收
}
// 3. 允许空值
_channel.value = null // 可以
StateFlow(电台广播):
// 1. 必须有初始值
private val _channel = MutableStateFlow<String>("初始频道")
// 2. 需要手动处理生命周期
lifecycleScope.launch {
viewModel.channel
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
// 需要手动处理生命周期
}
}
// 3. 不建议使用空值
_channel.value = null // 不推荐
6. 实际应用场景
class UserViewModel : ViewModel() {
// 1. 用户状态(适合 StateFlow)
private val _userState = MutableStateFlow<UserState>(UserState.Initial)
val userState = _userState.asStateFlow()
// 2. 一次性事件(适合 LiveData)
private val _toastEvent = MutableLiveData<String>()
val toastEvent: LiveData<String> = _toastEvent
}
// 使用
class UserFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 1. 观察持续状态
viewLifecycleOwner.lifecycleScope.launch {
viewModel.userState.collect { state ->
when(state) {
is UserState.Loading -> showLoading()
is UserState.Success -> showContent(state.user)
is UserState.Error -> showError(state.message)
}
}
}
// 2. 观察一次性事件
viewModel.toastEvent.observe(viewLifecycleOwner) { message ->
showToast(message)
}
}
}
7. 选择建议
graph LR
A[选择依据] --> B[使用 LiveData]
A --> C[使用 StateFlow]
B --> B1[UI相关数据]
B --> B2[一次性事件]
B --> B3[需要生命周期感知]
C --> C1[状态管理]
C --> C2[数据流处理]
C --> C3[需要初始值]
总结:
-
LiveData 适合:
- UI 相关的数据
- 一次性事件
- 需要自动处理生命周期
- 像电视:打开才接收
-
StateFlow 适合:
- 状态管理
- 数据流处理
- 需要初始值的场景
- 像广播:持续发送
-
主要区别:
- 初始值要求
- 生命周期处理
- 空值处理
- 热流/冷流特性
-
使用建议:
- UI 事件用 LiveData
- 状态管理用 StateFlow
- 考虑是否需要初始值
- 考虑生命周期处理需求
记住:
- LiveData 像电视:打开才能看到节目
- StateFlow 像广播:一直在播放
- 根据实际需求选择合适的工具
再来个Channel、LiveData 和 StateFlow三者的对比
让我用生动的比喻来对比 Channel、LiveData 和 StateFlow 在 Compose 中的使用:
1. 三者的特点比喻
想象一个餐厅的不同服务方式:
- Channel: 像快递员(一次性传递,送完就走)
- StateFlow: 像电子菜单(始终显示最新状态)
- LiveData: 像传统菜单(只在营业时更新)
2. 代码对比
class RestaurantViewModel : ViewModel() {
// 1. Channel - 一次性事件(像快递)
private val _orderChannel = Channel<String>()
val orderChannel = _orderChannel.receiveAsFlow()
// 2. StateFlow - 状态管理(像电子菜单)
private val _menuState = MutableStateFlow<List<Dish>>(emptyList())
val menuState = _menuState.asStateFlow()
// 3. LiveData - 生命周期感知(像传统菜单)
private val _specialDish = MutableLiveData<Dish>()
val specialDish: LiveData<Dish> = _specialDish
}
3. 在 Compose 中使用
@Composable
fun RestaurantScreen(viewModel: RestaurantViewModel) {
// 1. Channel 使用(不推荐)
LaunchedEffect(Unit) {
viewModel.orderChannel.collect { order ->
// 处理一次性事件
showToast(order)
}
}
// 2. StateFlow 使用(推荐)
val menuState by viewModel.menuState.collectAsStateWithLifecycle()
MenuList(dishes = menuState)
// 3. LiveData 使用
val specialDish by viewModel.specialDish.observeAsState()
SpecialDishCard(dish = specialDish)
}
4. 流程对比图
graph TD
A[数据传递方式] --> B[Channel]
A --> C[StateFlow]
A --> D[LiveData]
B --> B1[一次性事件]
B --> B2[消费后消失]
B --> B3[需要手动处理生命周期]
C --> C1[状态管理]
C --> C2[保持最新状态]
C --> C3[适合Compose]
D --> D1[生命周期感知]
D --> D2[UI数据]
D --> D3[传统Android视图]
5. 为什么 Compose 中较少使用 Channel?
// ❌ Channel 在 Compose 中的问题
class OrderViewModel : ViewModel() {
private val _orderChannel = Channel<String>()
val orderChannel = _orderChannel.receiveAsFlow()
fun placeOrder() {
viewModelScope.launch {
_orderChannel.send("订单已下达")
// 问题:
// 1. 如果 Compose 重组,可能会错过事件
// 2. 需要额外的 LaunchedEffect 处理
// 3. 生命周期管理复杂
}
}
}
// ✅ 使用 StateFlow 更适合
class OrderViewModel : ViewModel() {
private val _orderState = MutableStateFlow<OrderState>(OrderState.Initial)
val orderState = _orderState.asStateFlow()
fun placeOrder() {
_orderState.value = OrderState.OrderPlaced
// 优势:
// 1. 状态始终保持
// 2. Compose 自动响应状态变化
// 3. 生命周期管理简单
}
}
问题详解
1. Channel vs StateFlow 在 Compose 中的区别
想象一个餐厅的两种通知方式:
Channel (像服务员喊单):
// 服务员喊:"12号桌的牛排好了!"
class WaiterViewModel : ViewModel() {
private val _orderChannel = Channel<String>()
fun notifyOrder() {
viewModelScope.launch {
_orderChannel.send("12号桌牛排好了")
// 问题:
// 1. 如果顾客在洗手间,就会错过这个通知
// 2. 需要专人一直守着听通知
// 3. 不知道当前订单状态
}
}
}
StateFlow (像电子显示屏):
// 电子显示屏实时显示:"12号桌:牛排制作中 -> 已完成"
class DisplayViewModel : ViewModel() {
private val _orderState = MutableStateFlow(OrderState.Cooking)
fun updateOrder() {
_orderState.value = OrderState.Ready
// 优势:
// 1. 顾客随时可以看到当前状态
// 2. 状态一直在那里,不会错过
// 3. 自动更新显示
}
}
2. Compose 重组带来的问题
@Composable
fun OrderScreen(viewModel: OrderViewModel) {
// ❌ Channel 方式:可能错过事件
LaunchedEffect(Unit) {
viewModel.orderChannel.collect { message ->
// 如果屏幕旋转或重组,
// 之前发送的消息就永远丢失了
showToast(message)
}
}
// ✅ StateFlow 方式:始终能获取最新状态
val orderState by viewModel.orderState.collectAsStateWithLifecycle()
when (orderState) {
OrderState.Cooking -> ShowCookingUI()
OrderState.Ready -> ShowReadyUI()
}
}
3. 其他潜在问题
- 内存泄漏风险:
// Channel 需要手动关闭
class RiskyViewModel : ViewModel() {
private val channel = Channel<String>()
override fun onCleared() {
super.onCleared()
channel.close() // 如果忘记关闭,可能导致泄漏
}
}
- 背压处理:
// Channel 在缓冲区满时会挂起
class BackpressureViewModel : ViewModel() {
// 缓冲区满时可能阻塞
private val channel = Channel<String>(Channel.BUFFERED)
fun sendMultipleOrders() {
viewModelScope.launch {
repeat(100) {
channel.send("Order $it") // 可能被阻塞
}
}
}
}
- 测试难度:
// StateFlow 更容易测试
@Test
fun testOrderState() {
val viewModel = OrderViewModel()
viewModel.placeOrder()
assertEquals(OrderState.OrderPlaced, viewModel.orderState.value)
}
// Channel 测试较复杂
@Test
fun testOrderChannel() = runTest {
val viewModel = OrderViewModel()
var received = false
// 需要额外的协程作用域和收集逻辑
viewModel.orderChannel.collect {
received = true
}
viewModel.placeOrder()
assertTrue(received)
}
4. 建议的解决方案
class ImprovedViewModel : ViewModel() {
// 1. 使用 StateFlow 管理 UI 状态
private val _uiState = MutableStateFlow<UiState>(UiState.Initial)
val uiState = _uiState.asStateFlow()
// 2. 如果确实需要一次性事件,使用 SharedFlow
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun processOrder() {
viewModelScope.launch {
try {
_uiState.value = UiState.Processing
// 处理订单
_uiState.value = UiState.Success
// 发送一次性通知
_events.emit(UiEvent.OrderSuccess)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
记住:
- Channel 像喊单:可能会错过
- StateFlow 像显示屏:随时可查
- 在 Compose 中优先使用 StateFlow
- 需要一次性事件时考虑 SharedFlow
- 避免直接使用 Channel 处理 UI 事件
6. 最佳实践示例
class RestaurantViewModel : ViewModel() {
// 1. 使用 StateFlow 管理 UI 状态
private val _uiState = MutableStateFlow<UiState>(UiState.Initial)
val uiState = _uiState.asStateFlow()
// 2. 使用 SharedFlow 处理一次性事件(如果必需)
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun processOrder() {
viewModelScope.launch {
try {
// 更新状态
_uiState.value = UiState.Loading
// 处理订单
processOrderUseCase()
// 更新成功状态
_uiState.value = UiState.Success
// 发送一次性事件
_events.emit(UiEvent.ShowToast("订单成功"))
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
@Composable
fun RestaurantScreen(viewModel: RestaurantViewModel) {
// 1. 收集状态
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 2. 处理一次性事件
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is UiEvent.ShowToast -> showToast(event.message)
}
}
}
// 3. 根据状态显示 UI
when (uiState) {
is UiState.Loading -> LoadingSpinner()
is UiState.Success -> SuccessContent()
is UiState.Error -> ErrorMessage(uiState.message)
}
}
总结:
-
为什么较少使用 Channel:
- 不适合状态管理
- 可能丢失事件
- 生命周期管理复杂
-
StateFlow 的优势:
- 完美契合 Compose 的状态管理
- 保证状态一致性
- 自动处理重组
-
最佳实践:
- 使用 StateFlow 管理 UI 状态
- 必要时使用 SharedFlow 处理事件
- 避免使用 Channel 和 LiveData
记住:
- Channel 像快递员:送一次就走
- StateFlow 像电子菜单:随时更新
- LiveData 像传统菜单:需要人工更新
在 Compose 中:
- 优先使用 StateFlow 管理状态
- 状态驱动 UI 更新
- 保持简单和可预测性
冷流热流
1. 热流vs冷流的比喻
想象一个直播间:
- 热流:像直播(无论有没有观众都在直播)
- 冷流:像点播(观众请求才开始播放)
2. 三者的流程图
graph TD
A[数据流类型] --> B[热流]
A --> C[冷流]
B --> D[StateFlow]
B --> E[SharedFlow]
C --> F[Channel]
C --> G[Flow]
D --> D1[始终保持最新值]
D --> D2[多个订阅者共享]
D --> D3[必须有初始值]
E --> E1[多个订阅者共享]
E --> E2[可配置回放]
E --> E3[无需初始值]
F --> F1[一对一传递]
F --> F2[消费即销毁]
F --> F3[缓冲区满则挂起]
G --> G1[每个收集者独立]
G --> G2[按需计算]
G --> G3[冷启动]
3. 代码示例
class StreamViewModel : ViewModel() {
// 1. StateFlow(热流)
private val _stateFlow = MutableStateFlow("初始值")
val stateFlow = _stateFlow.asStateFlow()
// 2. Channel(冷流)
private val _channel = Channel<String>()
val channelFlow = _channel.receiveAsFlow()
// 3. SharedFlow(热流)
private val _sharedFlow = MutableSharedFlow<String>()
val sharedFlow = _sharedFlow.asSharedFlow()
fun updateData() {
viewModelScope.launch {
// StateFlow: 更新值,所有观察者都能收到
_stateFlow.value = "新数据"
// Channel: 发送数据,只有一个观察者能收到
_channel.send("新数据")
// SharedFlow: 发送数据,所有观察者都能收到
_sharedFlow.emit("新数据")
}
}
}
4. 特性对比表
graph TD
A[特性对比] --> B[StateFlow]
A --> C[Channel]
A --> D[SharedFlow]
B --> B1[热流]
B --> B2[需要初始值]
B --> B3[保留最新值]
C --> C1[冷流]
C --> C2[无需初始值]
C --> C3[一次性消费]
D --> D1[热流]
D --> D2[无需初始值]
D --> D3[可配置回放]
5. 使用场景
class ExampleViewModel : ViewModel() {
// 1. StateFlow:UI状态管理(热流)
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
// 2. Channel:一次性事件(冷流)
private val _events = Channel<Event>()
val events = _events.receiveAsFlow()
// 3. SharedFlow:多订阅者事件(热流)
private val _notifications = MutableSharedFlow<String>()
val notifications = _notifications.asSharedFlow()
}
// 使用示例
@Composable
fun ExampleScreen(viewModel: ExampleViewModel) {
// 1. 收集 StateFlow(UI状态)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 2. 收集 Channel(一次性事件)
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
handleEvent(event)
}
}
// 3. 收集 SharedFlow(通知)
LaunchedEffect(Unit) {
viewModel.notifications.collect { notification ->
showNotification(notification)
}
}
}
6. 总结特点
-
StateFlow(热流):
- 必须有初始值
- 保留最新状态
- 多个订阅者共享数据
- 适合UI状态管理
-
Channel(冷流):
- 无需初始值
- 一次性消费
- 点对点传递
- 适合一次性事件
-
SharedFlow(热流):
- 无需初始值
- 可配置回放策略
- 多个订阅者共享
- 适合多订阅者场景
使用建议:
graph TD
A[选择流类型] --> B{是否需要状态?}
B -->|是| C[StateFlow]
B -->|否| D{是否多订阅者?}
D -->|是| E[SharedFlow]
D -->|否| F[Channel]
记住:
- 热流像直播:随时都在播放
- 冷流像点播:需要时才播放
- StateFlow/SharedFlow 是热流
- Channel/Flow 是冷流
选择建议:
- UI状态管理:用 StateFlow
- 一次性事件:用 Channel
- 多订阅者事件:用 SharedFlow
总结下LiveData为什么不适合Compose
让我用生动的例子来解释为什么LiveData不适合在Compose中使用。
主要原因:
- 生命周期感知问题:
// LiveData依赖生命周期
class OldWay {
private val _data = MutableLiveData<String>()
// 需要LifecycleOwner来观察
fun observe(lifecycleOwner: LifecycleOwner) {
_data.observe(lifecycleOwner) { data ->
// 更新UI
}
}
}
// Compose是声明式的,不需要生命周期
@Composable
fun NewWay(state: State<String>) {
// 直接使用状态
Text(text = state.value)
}
- 状态更新机制不同:
// LiveData的命令式更新
class LiveDataExample {
private val _counter = MutableLiveData(0)
fun increment() {
_counter.value = _counter.value!! + 1 // 需要处理空值
}
}
// Compose的声明式更新
@Composable
fun ComposeExample() {
var counter by remember { mutableStateOf(0) }
Button(onClick = { counter++ }) {
Text("Count: $counter")
}
}
更好的选择:
- 使用StateFlow:
class ModernViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
fun updateState() {
_uiState.update { currentState ->
currentState.copy(
data = "新数据"
)
}
}
}
@Composable
fun MyScreen(viewModel: ModernViewModel) {
// 自动处理生命周期
val uiState by viewModel.uiState.collectAsState()
// 使用状态
MyContent(uiState)
}
- 使用State:
class ComposeViewModel : ViewModel() {
// 使用MutableState
private val _state = mutableStateOf(UiState())
val state: State<UiState> = _state
fun updateState() {
_state.value = _state.value.copy(
isLoading = false
)
}
}
实际应用示例:
- 列表展示:
// ❌ LiveData方式
class ListViewModel : ViewModel() {
private val _items = MutableLiveData<List<Item>>()
val items: LiveData<List<Item>> = _items
}
// ✅ Compose方式
@Composable
fun ItemList(items: State<List<Item>>) {
LazyColumn {
items(items.value) { item ->
ItemRow(item)
}
}
}
- 表单处理:
// ❌ LiveData方式
class FormViewModel : ViewModel() {
private val _formState = MutableLiveData(FormState())
fun updateName(name: String) {
_formState.value = _formState.value?.copy(name = name)
}
}
// ✅ Compose方式
@Composable
fun Form() {
var name by remember { mutableStateOf("") }
TextField(
value = name,
onValueChange = { name = it }
)
}
生动比喻:
- LiveData像是"订阅报纸":
- 需要登记订阅(生命周期)
- 只有在订阅期间才能收到(生命周期感知)
- 需要人工处理投递和取消(手动观察和移除)
- Compose State像是"数字显示屏":
- 直接显示当前状态
- 自动更新显示
- 不需要特别管理
最佳实践:
- 使用StateFlow替代LiveData:
class BestPracticeViewModel : ViewModel() {
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
@Composable
fun Content() {
val uiState by state.collectAsState()
when (uiState) {
is Loading -> LoadingScreen()
is Success -> SuccessScreen(uiState.data)
is Error -> ErrorScreen(uiState.message)
}
}
}
- 使用remember和mutableStateOf:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
- 状态提升:
@Composable
fun StatefulCounter() {
var count by remember { mutableStateOf(0) }
StatelessCounter(count) { count++ }
}
@Composable
fun StatelessCounter(
count: Int,
onIncrement: () -> Unit
) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
关键点总结:
- LiveData依赖生命周期,Compose不需要
- Compose使用声明式UI,更适合State
- StateFlow和State是更好的选择
- 状态管理更简单直接
- 代码更简洁清晰
就像是:
- LiveData是老式的电视机(需要调频、接收信号)
- Compose State是现代智能电视(直接显示内容)
这就是为什么在Compose中推荐使用StateFlow或State而不是LiveData!
LiveData粘性事件的底层原理
flowchart TD
A[LiveData 创建] --> B[设置初始值 mVersion = -1]
B --> C[setValue/postValue 更新数据]
C --> D[mVersion++]
D --> E[存储新值到 mData]
%% 观察者注册流程
F[观察者注册] --> G{是否是首次注册?}
G -->|是| H[创建 ObserverWrapper]
G -->|否| I[复用已有 Wrapper]
H --> J[存储观察者版本号 mLastVersion = -1]
I --> K[检查生命周期状态]
J --> K
%% 值分发流程
K --> L{是否处于活跃状态?}
L -->|是| M{比较版本号\nmLastVersion < mVersion?}
L -->|否| N[等待状态变为活跃]
M -->|是| O[更新 mLastVersion\n分发最新值]
M -->|否| P[不进行分发]
%% 子图:粘性事件原理
subgraph 粘性事件原理
Q[新观察者注册] --> R[版本号小于当前版本]
R --> S[立即收到最新值]
end
%% 子图:避免粘性事件
subgraph 避免粘性的方案
T[Event 包装类] --> U[标记是否已处理]
V[SingleLiveEvent] --> W[使用 AtomicBoolean 控制]
X[SharedFlow] --> Y[无粘性特性]
end
关键流程说明:
- 数据更新流程:
- LiveData 创建时 mVersion = -1
- 每次更新数据,mVersion 递增
- 新值存储在 mData 中
- 观察者注册流程:
- 观察者首次注册时 mLastVersion = -1
- 包装观察者并存储
- 检查生命周期状态
- 值分发机制:
- 比较 mLastVersion 和 mVersion
- 版本号不同时触发更新
- 更新观察者的版本号
- 粘性产生原因:
- 新观察者版本号为 -1
- 当前值版本号大于 -1
- 导致立即触发值的分发
- 解决方案:
- Event 包装类
- SingleLiveEvent
- 使用 SharedFlow
这个流程解释了为什么 LiveData 会有粘性事件,以及如何通过不同方案来避免粘性事件的发生。
补充一个具体的代码示例来展示这个流程:
// 1. 标准 LiveData 的粘性行为
class StickyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
init {
_data.value = "Initial Value" // 会触发版本号增加
}
}
// 2. 使用 Event 包装避免粘性
class NonStickyViewModel : ViewModel() {
private val _event = MutableLiveData<Event<String>>()
val event: LiveData<Event<String>> = _event
fun sendEvent() {
_event.value = Event("New Event")
}
}
// 3. 使用 SingleLiveEvent 避免粘性
class SingleEventViewModel : ViewModel() {
private val _event = SingleLiveEvent<String>()
val event: LiveData<String> = _event
fun sendEvent() {
_event.value = "New Event"
}
}
// 4. 使用 SharedFlow 避免粘性
class FlowViewModel : ViewModel() {
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
fun sendEvent() {
viewModelScope.launch {
_events.emit("New Event")
}
}
}
1. 粘性事件的表现:
class MyViewModel : ViewModel() {
private val _event = MutableLiveData<String>()
val event: LiveData<String> = _event
fun triggerEvent() {
_event.value = "Some event"
}
}
// Activity/Fragment
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 即使在设置值之后注册观察者,也能收到最后一次的值
viewModel.event.observe(this) { event ->
// 会收到之前的事件
}
}
}
2. 核心实现原理:
LiveData 的核心代码实现(简化版):
public abstract class LiveData<T> {
// 存储最新的值
private volatile Object mData = NOT_SET
// 版本号,用于追踪更新
private int mVersion = -1
// 观察者列表
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// ...
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// 关键点1:将观察者包装并存储
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// ...
// 关键点2:考虑当前生命周期状态,可能立即分发最新值
wrapper.activeStateChanged(shouldBeActive());
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// ...
do {
mDispatchInvalidated = false;
if (initiator != null) {
// 关键点3:比较版本号决定是否分发
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// 关键点4:版本号比较,决定是否需要更新
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
}
3. 如何避免粘性事件:
方法一:使用 Event 包装类
// 事件包装类
class Event<T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
fun peekContent(): T = content
}
// 使用示例
class MyViewModel : ViewModel() {
private val _event = MutableLiveData<Event<String>>()
val event: LiveData<Event<String>> = _event
fun triggerEvent() {
_event.value = Event("Some event")
}
}
// 在 UI 中使用
viewModel.event.observe(viewLifecycleOwner) { event ->
event.getContentIfNotHandled()?.let { value ->
// 只会处理一次
}
}
方法二:使用自定义 LiveData 观察者
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
}
// 使用示例
class MyViewModel : ViewModel() {
private val _event = SingleLiveEvent<String>()
val event: LiveData<String> = _event
fun triggerEvent() {
_event.value = "Some event"
}
}
4. 实际应用建议:
1. 对于 UI 状态:
// 使用普通 LiveData,需要粘性
class MyViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState>()
val uiState: LiveData<UiState> = _uiState
}
2. 对于一次性事件:
// 使用 Event 包装或 SingleLiveEvent
class MyViewModel : ViewModel() {
private val _navigation = SingleLiveEvent<NavigationEvent>()
val navigation: LiveData<NavigationEvent> = _navigation
}
3. 使用 Flow 替代:
class MyViewModel : ViewModel() {
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
fun triggerEvent() {
viewModelScope.launch {
_events.emit("Some event")
}
}
}
// 在 UI 中使用
lifecycleScope.launch {
viewModel.events.collect { event ->
// 不会收到历史事件
}
}
5. 注意事项:
1. 生命周期感知:
// 始终使用正确的 LifecycleOwner
viewModel.event.observe(viewLifecycleOwner) { // Fragment 中使用
// ...
}
viewModel.event.observe(this) { // Activity 中使用
// ...
}
2. 线程安全:
// LiveData 的值必须在主线程设置
viewModel.event.postValue("event") // 后台线程使用 postValue
viewModel.event.value = "event" // 主线程使用 value
3. 内存泄漏防护:
// LiveData 会自动处理生命周期,不需要手动移除观察者
// 但要确保使用正确的 LifecycleOwner
总结:
- LiveData 的粘性事件是通过版本号机制实现的
- 可以使用 Event 包装类或 SingleLiveEvent 避免粘性事件
- 根据实际需求选择合适的方案
- 考虑使用 Flow 作为现代替代方案