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 后才真正停止,可以平衡资源使用和响应性

所以它特别适合:

  • 需要共享的开销较大的数据流
  • 页面快速切换的场景
  • 需要缓存最新值的场景
相关推荐
黑码哥4 分钟前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓16 分钟前
[JDBC]元数据
android
独行soc27 分钟前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能35 分钟前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿36 分钟前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc1 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码20352 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106322 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
vistaup2 小时前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp
常利兵2 小时前
ButterKnife在Android 35 + Gradle 8.+环境下的适配困境与现代化迁移指南
android