一 两种 SharingStarted
策略的区别:
- SharingStarted.Eagerly:
- 立即开始收集上游流,即使没有下游订阅者
- 持续保持活跃状态,直到 ViewModel 被清除
- 优点:响应更快,数据始终保持最新
- 缺点:消耗更多资源,因为始终在收集数据
- SharingStarted.WhileSubscribed(5000):
- 仅在有下游订阅者时才开始收集
- 停止收集后等待 5000 毫秒才真正停止上游流
- 优点:更节省资源
- 缺点:首次订阅时可能有短暂延迟
选择建议:
// 适合使用 Eagerly 的场景:
// 1. 需要立即获取和保持数据最新状态
// 2. 数据更新频繁且重要的场景
val fragranceChannel = FragranceRepository.observeSelectedChannel().stateIn(viewModelScope, SharingStarted.Eagerly, DEFAULT_CHANNEL)// 适合使用 WhileSubscribed 的场景:
// 1. 数据不需要实时更新
// 2. 想要节省资源的场景
val acStatus = ACStatusRepository.acSwitchStatus.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
两种策略说明
这是一个典型的Flow订阅场景。解释上下游关系:
- 上游(Upstream):
// 在 ViewModel 中
val fragranceChannelMaterialCardView = ToggleButtonState(state = FragranceRepository.observeSelectedChannel() // 这是上游数据源.stateIn(viewModelScope, SharingStarted.Eagerly, DEFAULT_CHANNEL),...
)
- 下游(Downstream):
// 在 Fragment 中
viewModel.fragranceChannelMaterialCardView.state.collect { pos -> // 这是下游订阅者binding.fragranceSelectedChannel = pos
}
流程说明:
FragranceRepository.observeSelectedChannel()
产生数据.stateIn()
将Flow转换为StateFlow- Fragment中的
.collect
订阅这个StateFlow - 当上游数据变化时,下游会收到通知并更新UI
这就像一个管道:
数据源(Repository) -> StateFlow(ViewModel) -> 订阅者(Fragment)
[上游] [中转站] [下游]
使用 SharingStarted.Eagerly
意味着即使没有下游订阅,上游也会一直产生数据。
如果改用 WhileSubscribed
,只有在Fragment 订阅时才会开始收集数据。
二 SharingStarted.Eagerly示例
SharingStarted.Eagerly
的收集机制:
class WeatherViewModel : ViewModel() {// 上游数据源 - 模拟温度传感器private val temperatureSource = flow {var temp = 20while(true) {emit(temp++)delay(1000)println("上游发射温度: $temp") // 日志观察发射}}// 使用 Eagerly 立即开始收集val temperature = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly, // 立即开始收集initialValue = 0)init {// 可以在这里观察收集到的值viewModelScope.launch {temperature.collect { temp ->println("ViewModel 内部收到温度: $temp")}}}
}// UI层使用
class WeatherFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 即使这里暂时还没有收集,上游也在发射数据// 延迟5秒后再开始收集lifecycleScope.launch {delay(5000)viewModel.temperature.collect { temp ->binding.tempText.text = "$temp°C"}}}
}
关键点解释:
-
立即收集的意义:
- 即使没有下游订阅者,StateFlow 也会保持最新值
- 当下游开始订阅时,可以立即获得最新状态
- 适合需要持续监控或后台处理的场景
-
收集过程:
上游发射温度: 20 (立即开始)
上游发射温度: 21
上游发射温度: 22
上游发射温度: 23
上游发射温度: 24
[5秒后 Fragment 开始收集]
Fragment收到温度: 24 (立即获得最新值)
上游发射温度: 25
Fragment收到温度: 25
- 适用场景:
class LocationViewModel : ViewModel() {// 位置追踪需要持续进行,即使UI暂时不可见val location = locationManager.locationUpdates().stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = defaultLocation)
}
- 与 WhileSubscribed 的对比:
// WhileSubscribed - 有订阅者才收集
val temperature1 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = 0
)// Eagerly - 立即开始收集
val temperature2 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = 0
)
- 使用建议:
- 如果数据源消耗资源较大,建议使用
WhileSubscribed
- 如果需要后台持续处理或保持最新状态,使用
Eagerly
- 大多数UI状态场景,推荐使用
WhileSubscribed
class CarViewModel : ViewModel() {// 车辆状态需要持续监控 - 使用 Eagerlyval carStatus = carMonitor.status.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = CarStatus.Unknown)// UI展示数据 - 使用 WhileSubscribedval uiState = dataSource.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = UiState())
}
所以,Eagerly
的立即收集主要用于:
- 需要持续后台处理的场景
- 状态不能中断的监控场景
- 需要立即响应的关键数据流
三 SharingStarted.WhileSubscribed(5000) 示例
下面通过一个具体示例来说明下游停止收集的情况:
class TemperatureViewModel : ViewModel() {// 上游数据源private val temperatureSource = flow {var temp = 20while(true) {emit(temp++)delay(1000) }}// 使用 WhileSubscribed(5000) 转换为 StateFlowval temperature = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = 0)
}// Fragment/Activity 中使用
class TemperatureFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 开始收集 - 这时上游 flow 会开始发射数据viewLifecycleOwner.lifecycleScope.launch {viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.temperature.collect { temp ->binding.tempText.text = "$temp°C"}}}}
}
以下情况会导致下游停止收集:
- Fragment 进入 STOPPED 状态(如切到后台):
// repeatOnLifecycle 会在 STOPPED 时自动取消收集
// 但会等待 5000ms 后才真正停止上游 flow
onStop() {// 此时下游停止收集,但上游继续运行 5000ms
}
- 显式取消协程:
val job = lifecycleScope.launch {viewModel.temperature.collect { }
}// 取消协程会停止收集
job.cancel() // 上游会在 5000ms 后停止
- Fragment/Activity 销毁:
onDestroy() {// lifecycleScope 取消导致收集停止// 上游会在 5000ms 后停止
}
WhileSubscribed(5000)
的好处是:
- 短时间内重新订阅时(如快速切换页面)无需重启上游 flow
- 避免频繁启停上游带来的开销
- 5000ms 后才真正停止,可以平衡资源使用和响应性
所以它特别适合:
- 需要共享的开销较大的数据流
- 页面快速切换的场景
- 需要缓存最新值的场景