kotlin flow的两种SharingStarted策略的区别

一 两种 SharingStarted 策略的区别:

  1. SharingStarted.Eagerly:
  • 立即开始收集上游流,即使没有下游订阅者
  • 持续保持活跃状态,直到 ViewModel 被清除
  • 优点:响应更快,数据始终保持最新
  • 缺点:消耗更多资源,因为始终在收集数据
  1. 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订阅场景。解释上下游关系:

  1. 上游(Upstream)
// 在 ViewModel 中
val fragranceChannelMaterialCardView = ToggleButtonState(
    state = FragranceRepository.observeSelectedChannel()  // 这是上游数据源
        .stateIn(viewModelScope, SharingStarted.Eagerly, DEFAULT_CHANNEL),
    ...
)
  1. 下游(Downstream)
// 在 Fragment 中
viewModel.fragranceChannelMaterialCardView.state.collect { pos ->  // 这是下游订阅者
    binding.fragranceSelectedChannel = pos
}

流程说明:

  1. FragranceRepository.observeSelectedChannel() 产生数据
  2. .stateIn() 将Flow转换为StateFlow
  3. Fragment中的 .collect 订阅这个StateFlow
  4. 当上游数据变化时,下游会收到通知并更新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"
            }
        }
    }
}

关键点解释:

  1. 立即收集的意义

    • 即使没有下游订阅者,StateFlow 也会保持最新值
    • 当下游开始订阅时,可以立即获得最新状态
    • 适合需要持续监控或后台处理的场景
  2. 收集过程

上游发射温度: 20  (立即开始)
上游发射温度: 21
上游发射温度: 22
上游发射温度: 23
上游发射温度: 24
[5秒后 Fragment 开始收集]
Fragment收到温度: 24 (立即获得最新值)
上游发射温度: 25
Fragment收到温度: 25
  1. 适用场景
class LocationViewModel : ViewModel() {
    // 位置追踪需要持续进行,即使UI暂时不可见
    val location = locationManager.locationUpdates()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = defaultLocation
        )
}
  1. 与 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
)
  1. 使用建议
  • 如果数据源消耗资源较大,建议使用 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"
                }
            }
        }
    }
}

以下情况会导致下游停止收集:

  1. Fragment 进入 STOPPED 状态(如切到后台):
// repeatOnLifecycle 会在 STOPPED 时自动取消收集
// 但会等待 5000ms 后才真正停止上游 flow
onStop() {
    // 此时下游停止收集,但上游继续运行 5000ms
}
  1. 显式取消协程:
val job = lifecycleScope.launch {
    viewModel.temperature.collect { }
}

// 取消协程会停止收集
job.cancel() // 上游会在 5000ms 后停止
  1. Fragment/Activity 销毁:
onDestroy() {
    // lifecycleScope 取消导致收集停止
    // 上游会在 5000ms 后停止
}

WhileSubscribed(5000) 的好处是:

  • 短时间内重新订阅时(如快速切换页面)无需重启上游 flow
  • 避免频繁启停上游带来的开销
  • 5000ms 后才真正停止,可以平衡资源使用和响应性

所以它特别适合:

  • 需要共享的开销较大的数据流
  • 页面快速切换的场景
  • 需要缓存最新值的场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值