kotlin flow的两种SharingStarted策略的区别

一 两种 SharingStarted 策略的区别:

  1. SharingStarted.Eagerly:
  • 立即开始收集上游流,即使没有下游订阅者
  • 持续保持活跃状态,直到 ViewModel 被清除
  • 优点:响应更快,数据始终保持最新
  • 缺点:消耗更多资源,因为始终在收集数据
  1. SharingStarted.WhileSubscribed(5000):
  • 仅在有下游订阅者时才开始收集
  • 停止收集后等待 5000 毫秒才真正停止上游流
  • 优点:更节省资源
  • 缺点:首次订阅时可能有短暂延迟

选择建议:

kotlin 复制代码
// 适合使用 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)
kotlin 复制代码
// 在 ViewModel 中
val fragranceChannelMaterialCardView = ToggleButtonState(
    state = FragranceRepository.observeSelectedChannel()  // 这是上游数据源
        .stateIn(viewModelScope, SharingStarted.Eagerly, DEFAULT_CHANNEL),
    ...
)
  1. 下游(Downstream)
kotlin 复制代码
// 在 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 的收集机制:

kotlin 复制代码
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

  3. 适用场景

kotlin 复制代码
class LocationViewModel : ViewModel() {
    // 位置追踪需要持续进行,即使UI暂时不可见
    val location = locationManager.locationUpdates()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = defaultLocation
        )
}
  1. 与 WhileSubscribed 的对比
kotlin 复制代码
// 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
kotlin 复制代码
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) 示例

下面通过一个具体示例来说明下游停止收集的情况:

kotlin 复制代码
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 状态(如切到后台):
kotlin 复制代码
// repeatOnLifecycle 会在 STOPPED 时自动取消收集
// 但会等待 5000ms 后才真正停止上游 flow
onStop() {
    // 此时下游停止收集,但上游继续运行 5000ms
}
  1. 显式取消协程:
kotlin 复制代码
val job = lifecycleScope.launch {
    viewModel.temperature.collect { }
}

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

WhileSubscribed(5000) 的好处是:

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

所以它特别适合:

  • 需要共享的开销较大的数据流
  • 页面快速切换的场景
  • 需要缓存最新值的场景
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui