一 两种 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 = 20
while(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() {
// 车辆状态需要持续监控 - 使用 Eagerly
val carStatus = carMonitor.status.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = CarStatus.Unknown
)
// UI展示数据 - 使用 WhileSubscribed
val uiState = dataSource.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = UiState()
)
}
所以,Eagerly
的立即收集主要用于:
- 需要持续后台处理的场景
- 状态不能中断的监控场景
- 需要立即响应的关键数据流
三 SharingStarted.WhileSubscribed(5000) 示例
下面通过一个具体示例来说明下游停止收集的情况:
class TemperatureViewModel : ViewModel() {
// 上游数据源
private val temperatureSource = flow {
var temp = 20
while(true) {
emit(temp++)
delay(1000)
}
}
// 使用 WhileSubscribed(5000) 转换为 StateFlow
val 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 后才真正停止,可以平衡资源使用和响应性
所以它特别适合:
- 需要共享的开销较大的数据流
- 页面快速切换的场景
- 需要缓存最新值的场景