在 Kotlin 协程与 Flow 的生态中,stateIn() 是一个高频使用的操作符——它能够将冷流转换为热流,并提供一个可观察的状态快照。然而,许多开发者在使用时往往只关心如何获得一个 StateFlow,却忽略了第三个参数 SharingStarted 的决定性作用。事实上,stateIn() 的真正核心并非“如何创建状态”,而是“何时开始共享”——而这一切,都由 SharingStarted 掌控。

被低估的第三个参数

stateIn() 的完整签名为:

fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T>

前两个参数分别指定协程作用域和初始值,而 started 参数定义了上游 Flow 何时被订阅、何时停止。它直接决定了 StateFlow 的行为模式,是区分“轻量级观察”与“资源浪费”的关键。

对于尚未深入理解协程的开发者来说,SharingStarted 看上去只是三个简单的枚举选项。但当你遇到内存泄漏、数据过期或意外重算时,才会意识到它的分量。

三种策略,三种生命周期

SharingStarted 提供了三个预定义策略:

1. SharingStarted.Eagerly:立刻订阅,永不停止

一旦 stateIn() 被调用,上游 Flow 会立即开始执行,即使没有任何订阅者。这种模式适用于“数据必须在后台持续更新”的场景,比如传感器数据、股票行情等。但代价是:只要协程作用域未取消,上游 Flow 就会一直运行,可能造成不必要的计算(例如网络轮询)。

2. SharingStarted.Lazily:第一个订阅时启动,永不停止

上游 Flow 会在第一个外部订阅出现时启动,之后即使所有订阅者都消失,它也不会停止。这常用于“一旦有人观察,就一直保持最新状态”的需求,比如用户设置页面。好处是避免了“无人关注还浪费资源”,但坏处是——如果用户离开页面不再回来,后台协程依然存活,可能导致内存泄漏。

3. SharingStarted.WhileSubscribed():动态启停,智能节流

这才是真正的“杀手锏”。上游 Flow 会在有订阅者时运行,没有订阅者时自动停止(并释放资源)。同时,它支持两个可选参数:stopTimeoutMillisreplayExpirationMillis

  • stopTimeoutMillis:当最后一个订阅者消失后,等待指定毫秒再停止。这可以有效防止配置变更(如屏幕旋转)导致频繁重启。通常设置为 5000 毫秒即可。
  • replayExpirationMillis:在流停止后,缓存的数据会在指定时间后过期。过期后,下次订阅时将从初始值重新开始,而不是拿到过时的缓存。

例如:

flow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
    initialValue = null
)

这种模式非常适合 Android 的 UI 层:当用户离开屏幕,5 秒内无订阅则自动取消网络请求;若用户在 5 秒内返回,则直接使用最新缓存,无需重新请求。

实战误区:为何“无限期运行”不是好事?

很多新手直接使用 EagerlyLazily,认为“数据保持新鲜总没错”。但在实际业务中,后台无意义的持续运行会引发连锁问题:

  • 电池与流量消耗:后台虚占协程,可能触发网络请求或数据库查询。
  • 数据混乱:当上游 Flow 是数据库结合 Room 的 Flow 时,长期保持订阅可能导致数据库变更事件无限传递,引发 UI 频繁重绘。
  • 测试困难:由于协程永远不会被取消,单元测试中难以控制生命周期,容易产生内存泄漏。

最佳实践:按场景选择

场景 推荐策略 理由
长期运行的后台服务(如地理位置监听) Eagerly 必须立即启动且持续运行
用户主动打开的配置界面(如设置页) Lazily 用户进入时启动,离开后保留状态以便快速恢复
需要跟随 UI 生命周期的数据 WhileSubscribed(stopTimeoutMillis = 5000) 避免空闲时浪费,同时防止频繁重启
需要过期缓存的列表数据 WhileSubscribed(replayExpirationMillis = 30_000) 30 秒无观察则清空缓存,确保数据新鲜

结语

stateIn() 之所以强大,不是因为它能生成 StateFlow,而是它背后的 SharingStarted 提供了精确的生命周期控制。理解并善用 WhileSubscribed() 的智能节流机制,是优化 Android 应用性能、避免资源浪费的关键一步。下一次当你写下 stateIn() 时,不妨多花十秒钟思考:你的上游 Flow,真的需要在无人观察时继续运行吗?

(字数:约 1050 字)