前言
看到下面的业务场景,阁下改如何面对
Kotlin
// 一个下载任务,需要向多个模块实时传递进度
object DownloadManager {
private var currentProgress = 0
fun startDownload(url: String) {
// 下载逻辑...
currentProgress = progress
// 问题:进度怎么通知给外面?
}
}
同事的解决方案:写了个静态List存Callback。
我问他:为什么不用协程的Channel或Flow?
他说:这两个到底什么区别?什么时候用哪个?
这个问题我见过太多次了。今天我们就来彻底搞懂------协程之间到底怎么高效、安全地传递数据。
一、协程数据传递的四个层次
核心认知:
不同的传递场景,对应不同的协程工具。没有银弹,只有最适合。
二、场景1:单次传递 ------ async/await
适用场景: 一个协程等另一个协程的一个结果
Kotlin
// ✅ 标准用法:并行请求
viewModelScope.launch {
val result = coroutineScope {
val deferred1 = async { api.fetchUser() }
val deferred2 = async { api.fetchConfig() }
// 等待两个结果
UserWithConfig(deferred1.await(), deferred2.await())
}
}
// ✅ 异步转同步
suspend fun fetchData(): Data = coroutineScope {
val deferred = async(Dispatchers.IO) {
// 耗时操作
loadFromDisk()
}
deferred.await() // 挂起直到结果返回
}
本质: async 创建了一个带结果 的协程,await 是单次消费。
不适合: 需要多次发送进度、多个订阅者的场景。
三、场景2:多次传递 ------ Channel
适用场景: 一对一 的生产者-消费者模式,每次数据只能被一个消费者取走
基础用法:生产者-消费者
Kotlin
package com.example.cb
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
/**
* 生产者
*/
class DataProducer {
val channel : Channel<String> = Channel()
suspend fun produceData() {
repeat(5) {
val data = "data_$it"
println("生产:$data")
channel.send("data_$it")
delay(200)
}
channel.close()
}
}
/**
* 消费者
*/
class DataConsumer(private val name : String, private val channel : Channel<String>) {
suspend fun consumeData() {
println("consumeData:call ")
for (data in channel) {
println("$name 消费 $data")
delay(10)
}
// 另外一种消费方式
// channel.consumeEach {
// println("消费:$it")
// }
}
}
运行代码
Kotlin
val scope = CoroutineScope(Dispatchers.IO)
val producer = DataProducer()
val consumer = DataConsumer("张三", producer.channel)
scope.launch {
producer.produceData()
}
scope.launch {
consumer.consumeData()
}
输出结果
生产:data_0
张三 消费 data_0
生产:data_1
张三 消费 data_1
生产:data_2
张三 消费 data_2
生产:data_3
张三 消费 data_3
生产:data_4
张三 消费 data_4
PS: 切记生产者跟消费者必须是不同的协程中,否则只会生产一个数据,但是不会消费,无限被挂起
扇出模式:多个消费者抢数据
生产一个数据后,只有一个会接受到数据。把执行代码修改一下就可以看到:
Kotlin
val scope = CoroutineScope(Dispatchers.IO)
val producer = DataProducer()
val consumer0 = DataConsumer("张三", producer.channel)
val consumer1 = DataConsumer("李四", producer.channel)
scope.launch { producer.produceData() }
scope.launch { consumer0.consumeData() }
scope.launch { consumer1.consumeData() }
输出结果
生产:data_0
李四 消费 data_0
生产:data_1
张三 消费 data_1
生产:data_2
李四 消费 data_2
生产:data_3
张三 消费 data_3
生产:data_4
李四 消费 data_4
Channel 的容量选择(面试高频)
前面的是容量都是0, 但是生产速度大于消费速度时,是否要进行缓存。这个缓存的数量时可以设置。 例如设置了10个容量,当消费不到10个时还会继续生产,不会挂起。当大于等于10个时,如果没有出现新的消费者就不会被继续生产。
Kotlin
// 🔥 Channel 的四种容量模式
Channel<Int>() // 默认:RENDEZVOUS,0容量,必须同时等待
Channel<Int>(capacity = 1) // CONFLATED?不是!这是普通缓冲区
Channel<Int>(Channel.RENDEZVOUS) // 0容量,发送和接收必须同时
Channel<Int>(Channel.BUFFERED) // 64容量,系统默认
Channel<Int>(Channel.CONFLATED) // 1容量,但新数据覆盖旧数据
Channel<Int>(Channel.UNLIMITED) // 无限制,注意内存!
// CONFLATED 的特殊行为
val conflated = Channel<Int>(Channel.CONFLATED)
launch {
conflated.send(1)
conflated.send(2) // 1被覆盖
conflated.send(3) // 2被覆盖
conflated.close()
}
launch {
for (item in conflated) {
println(item) // 只输出:3
}
}
Channel 的核心特性:
-
✅ 一对一消费(扇出时竞争)
-
✅ 背压支持(send挂起,receive挂起)
-
❌ 多订阅者(每个数据只到一个订阅者)
-
❌ 粘性(新订阅者收不到历史数据)
场景3:多对多传递 ------ Flow
flow是什么
Flow 是冷流(Cold Stream)
Kotlin
// 定义 Flow
val flow = flow {
println("Flow 开始执行")
for (i in 1..3) {
delay(100)
emit(i)
}
}
// ❌ 此时什么都没发生
println("Flow 定义完成")
// ✅ 每次 collect,Flow 代码块重新执行
lifecycleScope.launch {
flow.collect { println("Collector1: $it") }
} // 输出:Flow 开始执行,1,2,3
lifecycleScope.launch {
flow.collect { println("Collector2: $it") }
} // 输出:Flow 开始执行,1,2,3(重新执行!)
冷流核心特征:
-
无消费,不生产 ------没有
collect,Flow 代码块不会执行 -
每次 collect 独立执行------两个订阅者互不干扰
-
数据不会被缓存------消费完就丢
SharedFlow/StateFlow 是热流(Hot Stream)
Kotlin
// MutableSharedFlow 是热流
val sharedFlow = MutableSharedFlow<Int>()
// 发送数据(不需要有人订阅)
viewModelScope.launch {
repeat(5) {
sharedFlow.emit(it)
delay(100)
}
}
// 订阅者1:只能收到订阅之后发送的数据
viewModelScope.launch {
delay(250) // 延迟订阅
sharedFlow.collect { println("A: $it") } // 输出:2,3,4
}
// 订阅者2:也收到同样的数据
viewModelScope.launch {
sharedFlow.collect { println("B: $it") } // 输出:0,1,2,3,4
}
| 维度 | Flow | SharedFlow/StateFlow |
|---|---|---|
| 执行时机 | 每次 collect 执行 | 启动后常驻内存 |
| 订阅者关系 | 独立执行 | 共享数据 |
| 缓存 | 无 | 可配置 replay |
| 典型场景 | 数据库查询、网络请求 | 事件总线、UI状态 |
基础用法:生产数据发送给所有的订阅者
Kotlin
class DataProducer2 {
private val _datas = MutableSharedFlow<String>()
val datas = _datas.asSharedFlow()
suspend fun produceData() {
repeat(5) {
val data = "data_$it"
println("生产:$data")
_datas.emit(data)
delay(200)
}
}
}
class DataConsumer2(private val name : String, private val datas : SharedFlow<String>) {
suspend fun consumeData() {
datas.collect {
println("$name 消费 $it")
delay(10)
}
}
}
运行代码
Kotlin
val scope = CoroutineScope(Dispatchers.IO)
val producer = DataProducer2()
val consumer0 = DataConsumer2("张三", producer.datas)
val consumer1 = DataConsumer2("李四", producer.datas)
scope.launch { producer.produceData() }
scope.launch { consumer0.consumeData() }
scope.launch { consumer1.consumeData() }
结果输出
生产:data_0
张三 消费 data_0
李四 消费 data_0
生产:data_1
张三 消费 data_1
李四 消费 data_1
生产:data_2
张三 消费 data_2
李四 消费 data_2
生产:data_3
张三 消费 data_3
李四 消费 data_3
生产:data_4
张三 消费 data_4
李四 消费 data_4
SharedFlow 的 replay 缓存
Kotlin
class StateHolder {
// replay = 1:新订阅者立即收到最后一个值
private val _state = MutableSharedFlow<State>(
replay = 1, // 🔥 关键配置:缓存最新值
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val state: SharedFlow<State> = _state.asSharedFlow()
suspend fun updateState(newState: State) {
_state.emit(newState)
}
}
// 使用
val holder = StateHolder()
holder.updateState(State.Loading)
// 延迟订阅的协程能立即收到 Loading 状态
lifecycleScope.launch {
holder.state.collect { state ->
println(state) // 立即输出:Loading
}
}
SharedFlow vs Channel 核心区别
Kotlin
// 🔥 最直观的区别演示
val channel = Channel<Int>()
val sharedFlow = MutableSharedFlow<Int>()
// 场景:两个订阅者
launch {
channel.consumeEach { println("A: $it") } // 只有一个能收到
}
launch {
channel.consumeEach { println("B: $it") } // 只有一个能收到
}
channel.send(42) // 要么A收,要么B收,不会都收到
launch {
sharedFlow.collect { println("A: $it") } // 都能收到
}
launch {
sharedFlow.collect { println("B: $it") } // 都能收到
}
sharedFlow.emit(42) // A和B都收到42
场景4:状态持有 ------ StateFlow
适用场景: 需要最新状态的场景,UI状态管理
StateFlow = SharedFlow(replay=1) + 状态压缩
Kotlin
class UserViewModel : ViewModel() {
// StateFlow 特点:
// 1. 必须有初始值
// 2. replay=1 固定
// 3. 数据相同时不发送(distinctUntilChanged)
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_uiState.value = UiState.Loading // 直接赋值
try {
val user = repository.getUser(id)
_uiState.value = UiState.Success(user)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
// Activity 订阅
lifecycleScope.launch {
viewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { state ->
when (state) {
is UiState.Success -> showUser(state.user)
is UiState.Loading -> showLoading()
is UiState.Error -> showError(state.message)
else -> {}
}
}
}
5.2 StateFlow 去重特性
val state = MutableStateFlow(0) state.value = 1 // 发送 state.value = 1 // ❌ 值没变,不发送 state.value = 2 // 发送 state.value = 2 // ❌ 不发送 // 如果需要每次都发送,用 SharedFlow
实战对比:进度传递的四种方案
场景:下载任务需要把进度实时传给多个UI组件
❌ 方案1:Channel(错误示范
Kotlin
// Channel 不适合一对多广播
class DownloadManager {
private val progressChannel = Channel<Int>(Channel.CONFLATED)
fun startDownload() {
repeat(100) {
progressChannel.send(it) // ❌ 只有一个订阅者能收到
}
}
}
// 进度条A能收到,进度条B收不到
✅ 方案2:SharedFlow(正确方案)
Kotlin
class DownloadManager {
private val _progress = MutableSharedFlow<Int>(
replay = 0, // 不需要历史
extraBufferCapacity = 10,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val progress: SharedFlow<Int> = _progress.asSharedFlow()
suspend fun download(url: String) {
repeat(100) {
delay(50)
_progress.emit(it) // ✅ 所有订阅者都收到
}
}
}
// 多个UI组件独立订阅
class ProgressBar1 {
fun bind(manager: DownloadManager) {
lifecycleScope.launch {
manager.progress.collect { progress ->
setProgress(progress) // 收到进度
}
}
}
}
class ProgressBar2 {
fun bind(manager: DownloadManager) {
lifecycleScope.launch {
manager.progress.collect { progress ->
setProgress(progress) // 也收到进度
}
}
}
}
✅ 方案3:StateFlow(如果需显示当前进度)
Kotlin
class DownloadManager {
private val _progress = MutableStateFlow(0) // 初始值0
val progress: StateFlow<Int> = _progress.asStateFlow()
fun download(url: String) {
viewModelScope.launch {
repeat(100) {
delay(50)
_progress.value = it // 自动去重,但这里值都不同
}
}
}
}
// 新订阅者立即看到当前进度
✅ 方案4:Callback(简单场景)
Kotlin
// 如果只在单个页面用,Callback 依然简单有效
fun download(url: String, onProgress: (Int) -> Unit) {
thread {
repeat(100) {
Thread.sleep(50)
onProgress(it)
}
}
}
七、终极选择指南
对比总表
| 特性 | Channel | SharedFlow | StateFlow | async/await |
|---|---|---|---|---|
| 数据次数 | 多次 | 多次 | 多次 | 单次 |
| 订阅者模式 | 竞争消费 | 广播 | 广播 | 一对一 |
| 初始值 | ❌ | ❌ | ✅ | ❌ |
| 粘性 | ❌ | replay控制 | ✅ (replay=1) | ❌ |
| 背压 | ✅ send挂起 | ✅ emit挂起 | ✅ 赋值不挂起 | ❌ |
| 去重 | ❌ | ❌ | ✅ | ❌ |
| 取消 | close() | 无 | 无 | 无 |
| 典型场景 | 任务队列 | 事件总线 | UI状态 | 并行请求 |
一句话选型
- 要一个结果:async/await
- 要任务队列:Channel
- 要事件广播:SharedFlow
- 要UI状态:StateFlow
- 不会用协程的人:Callback
面试高频题
Q1:SharedFlow 和 StateFlow 的区别?
答:
StateFlow 必须有初始值,SharedFlow 不需要
StateFlow 自动去重,SharedFlow 发送全部数据
StateFlow 继承自 SharedFlow,replay=1 固定
StateFlow 通过
value读写,SharedFlow 通过emit/collect
Q2:Channel 和 Flow 怎么选?
答:
Channel 是热流,生产者和消费者直接耦合
Flow 是冷流,每次 collect 重新执行
需要背压 用 Channel,需要多订阅者用 SharedFlow
Q3:SharedFlow 缓冲区满了怎么办?
答: 三种策略:
SUSPEND:挂起发送方
DROP_OLDEST:丢弃最旧数据
DROP_LATEST:丢弃最新数据
写在最后
协程间数据传递,本质是并发模型的选择。
不懂 Channel,不知道怎么写生产者-消费者;
不懂 SharedFlow,不知道怎么做事件总线;
不懂 StateFlow,不知道怎么做状态管理。
但最可怕的不是不懂,而是用错了工具------
-
用 Channel 做广播 → 丢数据
-
用 StateFlow 做事件 → 丢事件
-
用 async 做多次传递 → 只能等一次
