使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow
使用共享运算符将冷 Flow 转换为热流。
使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。
冷流与热流
冷流会为每个收集器重新启动;热流共享一个上游订阅。将冷流转换为热流,可以在多个收集器订阅时避免重复工作。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveFlow() = flow {
println("Starting upstream work") // runs once if shared
repeat(3) { delay(100); emit(it) }
}
fun main() = runBlocking {
val cold = expensiveFlow()
// Two collectors = two executions:
launch { cold.collect { } }
launch { cold.collect { } }
delay(500)
coroutineContext.cancelChildren()
}shareIn 基础
flow.shareIn(scope, started, replay) 会将冷 Flow 转换为 SharedFlow,并共享一个上游订阅。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow {
println("Upstream started once")
repeat(5) { delay(100); emit(it) }
}
val hot = cold.shareIn(this, SharingStarted.Eagerly, replay = 0)
launch { hot.collect { println("A: $it") } }
launch { hot.collect { println("B: $it") } }
delay(600)
coroutineContext.cancelChildren()
}stateIn 基础
flow.stateIn(scope, started, initialValue) 会将流转换为 StateFlow——它始终有一个值,并重放 1 个值。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow { delay(100); emit(42) }
val state: StateFlow<Int> = cold.stateIn(
scope = this,
started = SharingStarted.Eagerly,
initialValue = 0
)
println(state.value) // 0 immediately
delay(200)
println(state.value) // 42 after upstream emits
coroutineContext.cancelChildren()
}SharingStarted.立即启动
立即启动:无论是否有订阅者,调用 shareIn/stateIn 时上游都会立即启动。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Eagerly started")
emit(1)
}.shareIn(this, SharingStarted.Eagerly)
// Upstream already running even before any collect
delay(50)
launch { flow.collect { println(it) } }
delay(100)
coroutineContext.cancelChildren()
}SharingStarted.延迟启动
延迟启动:上游会在第一个订阅者订阅时启动,并且永不停止(即使订阅者数量降为零)。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Lazily started on first subscriber")
repeat(3) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.Lazily, replay = 1)
delay(50) // no subscriber yet — not started
launch { flow.collect { println(it) } } // triggers start
delay(400)
coroutineContext.cancelChildren()
}SharingStarted.WhileSubscribed
WhileSubscribed(stopTimeout):第一个订阅者加入时启动上游,最后一个订阅者离开 stopTimeout 毫秒后停止。非常适合 ViewModels。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = flow {
println("Started")
repeat(10) { delay(100); emit(it) }
}.shareIn(this, SharingStarted.WhileSubscribed(500))
val job = launch { flow.collect { print("$it ") } }
delay(300)
job.cancel() // subscriber left
delay(200) // within 500ms stop timeout — still running
launch { flow.collect { print("resume $it ") } }
delay(500)
coroutineContext.cancelChildren()
}典型的 ViewModel 模式
在 Android ViewModels 中,使用 stateIn(viewModelScope, WhileSubscribed(5000), initialValue) 转换仓库流,以便共享流并在配置更改后继续保留。
import kotlinx.coroutines.flow.*
// In ViewModel:
// val uiState: StateFlow<UiState> = repository
// .dataFlow()
// .map { UiState.Success(it) }
// .stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(5_000),
// initialValue = UiState.Loading
// )
fun main() { println("WhileSubscribed(5000) is the recommended ViewModel pattern") }shareIn 与 stateIn
shareIn → SharedFlow(没有当前值,可配置重放)。stateIn → StateFlow(始终有一个值,重放值为 1)。请根据消费者是否需要当前状态进行选择。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = (1..3).asFlow().map { it * 10 }
// SharedFlow — no initial value:
val shared: SharedFlow<Int> = cold.shareIn(this, SharingStarted.Eagerly, replay = 1)
// StateFlow — always has a value:
val state: StateFlow<Int> = cold.stateIn(this, SharingStarted.Eagerly, 0)
delay(100)
println("shared cache: ${shared.replayCache}")
println("state value: ${state.value}")
coroutineContext.cancelChildren()
}重放缓存的权衡
重放值越大,较晚订阅的订阅者看到的历史越多,但占用的内存也越多。对于界面状态,重放值为 1(stateIn)就足够了。对于事件日志,可能需要更大的重放值。
import kotlinx.coroutines.flow.*
// replay=0: no history, only future events
// replay=1: last value (equivalent to stateIn)
// replay=N: last N events — use for message feeds, logs
// Trade-off: memory vs subscriber freshness
fun main() { println("Choose replay based on late-subscriber requirements") }未共享流的风险
如果不进行共享,每个 Compose 收集器或 ViewModel 观察者都会重新运行上游流,从而产生重复的网络请求和数据库查询。请始终共享开销较大的流。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveApi() = flow {
println("API CALL") // without sharing: once per collector
emit("data")
}
fun main() = runBlocking {
val shared = expensiveApi().shareIn(this, SharingStarted.Lazily, replay = 1)
launch { shared.collect { } } // one API call, shared
launch { shared.collect { } } // same emission
delay(200)
coroutineContext.cancelChildren()
}共享时的资源清理
共享流的作用域被取消时,上游流也会被取消,其 finally 代码块会执行,从而正确清理资源。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun resourceFlow() = flow {
try { repeat(10) { delay(100); emit(it) } }
finally { println("Upstream cleaned up") }
}
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
val shared = resourceFlow().shareIn(scope, SharingStarted.Eagerly)
launch { shared.collect { print("$it ") } }
delay(250)
scope.cancel() // upstream cleanup runs
delay(100)
}快速检查
对于 Android ViewModels,推荐使用哪种 SharingStarted 策略?
回顾
shareIn 会转换为 SharedFlow;stateIn 会转换为 StateFlow。在 ViewModels 中使用 WhileSubscribed 可以节省资源。当多个收集器订阅时,共享可以避免重复执行上游流。
常见问题解答
「使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow」课时是免费的吗?
是的 — 「使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。
「使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow」这节课中我会学到什么?
使用共享运算符将冷 Flow 转换为热流。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Kotlin Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Kotlin Academy 课中编写并运行代码吗?
能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- StateFlow:用于界面的热状态容器
- SharedFlow:事件总线与一次性事件
- 使用 shareIn 与 stateIn 将冷 Flow 转换为热 Flow
- 使用 Turbine 测试 StateFlow 与 SharedFlow