让我用一个简单的比喻开始:
- Channel 就像 快递员送货(点对点,确保送达)
- SharedFlow 就像 广播电台(一对多,发送即完成)
📊 核心区别对比
| 维度 | SharedFlow | Channel |
|---|---|---|
| 核心模型 | 广播模型 (Broadcast) | 队列模型 (Queue) |
| 消费方式 | 多个订阅者收到相同的数据 | 一条消息只能被一个消费者接收 |
| 数据状态 | 数据是瞬时事件 | 数据是待处理的任务 |
| 背压策略 | 可配置缓冲,可丢弃数据 | 多种缓冲策略,可阻塞或丢弃 |
| 订阅关系 | 1:N (一个生产者,多个消费者) | 1:1 (一条消息只能被消费一次) |
| 典型场景 | 状态更新、事件通知 | 任务队列、工作分发 |
🔍 本质区别详解
1. 消费语义的根本差异
这是最关键的区别:
kotlin
// SharedFlow - 广播语义
val sharedFlow = MutableSharedFlow<Event>()
sharedFlow.emit(Event.CLICK) // 发射后,所有订阅者都能收到
// Channel - 队列语义
val channel = Channel<Event>()
channel.send(Event.CLICK) // 发送后,只能有一个接收者能收到
2. 代码示例对比
让我们看一个具体的例子,说明为什么不能相互替代:
kotlin
class EventProcessor {
// ====== 使用 SharedFlow ======
private val _events = MutableSharedFlow<Event>(replay = 0)
val events = _events.asSharedFlow()
suspend fun processWithSharedFlow() {
// 模拟两个消费者
launch {
events.collect { event ->
println("消费者1: $event")
delay(100)
}
}
launch {
events.collect { event ->
println("消费者2: $event") // ⚠️ 两个消费者都会收到相同事件!
delay(100)
}
}
// 发送事件
_events.emit(Event("click"))
_events.emit(Event("swipe"))
// 输出:
// 消费者1: Event(click)
// 消费者2: Event(click) ← 同一个事件被处理两次!
// 消费者1: Event(swipe)
// 消费者2: Event(swipe) ← 重复处理问题!
}
// ====== 使用 Channel ======
private val eventChannel = Channel<Event>(capacity = 10)
suspend fun processWithChannel() {
// 工作协程 - 处理事件
repeat(3) { workerId ->
launch {
for (event in eventChannel) {
println("工作者$workerId 处理: $event")
delay(100) // 模拟处理时间
}
}
}
// 发送事件
eventChannel.send(Event("click"))
eventChannel.send(Event("swipe"))
eventChannel.send(Event("tap"))
eventChannel.send(Event("longPress"))
delay(500)
eventChannel.close()
// 输出示例:
// 工作者0 处理: Event(click) ← 每个事件只被处理一次
// 工作者1 处理: Event(swipe)
// 工作者2 处理: Event(tap)
// 工作者0 处理: Event(longPress) ← 工作协程轮流处理
}
}
🎯 使用场景决策树
需要处理事件/任务
有多少个消费者?
多个消费者
所有消费者都需要
处理同一事件吗?
是 - 广播场景
否 - 负载均衡场景
使用 SharedFlow
使用 Channel
单个消费者
需要确保事件被处理吗?
是 - 确保送达
否 - 尽力而为
使用 Channel
使用 SharedFlow
或 Channel CONFLATED
🚀 实际应用场景
适合 SharedFlow 的场景
kotlin
// 1. UI 状态更新(多界面观察同一状态)
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow(UserState()) // StateFlow 继承自 SharedFlow
val userState = _userState.asStateFlow() // 所有 Fragment 都观察同一状态
fun updateUser() {
_userState.value = newState // 所有观察者都收到更新
}
}
// 2. 全局事件总线
object EventBus {
private val _events = MutableSharedFlow<AppEvent>(replay = 0)
val events = _events.asSharedFlow()
suspend fun sendEvent(event: AppEvent) {
_events.emit(event) // 所有订阅的组件都会收到
}
}
// 3. 实时数据广播(如位置更新)
class LocationManager {
private val _locationFlow = MutableSharedFlow<Location>(replay = 1)
val locationFlow = _locationFlow.asSharedFlow()
fun updateLocation(location: Location) {
_locationFlow.tryEmit(location) // 多个地图组件都能收到
}
}
适合 Channel 的场景
kotlin
// 1. 工作队列/线程池模式
class TaskProcessor {
private val taskChannel = Channel<Task>(Channel.UNLIMITED)
init {
// 启动固定数量的工作协程
repeat(4) { workerId ->
launch {
for (task in taskChannel) {
processTask(task) // 每个任务只被一个工作者处理
}
}
}
}
fun submitTask(task: Task) {
taskChannel.trySend(task)
}
}
// 2. 请求-响应模式(需要确保响应匹配)
class RequestHandler {
private val requestChannel = Channel<Pair<Request, CompletableDeferred<Response>>>()
init {
launch {
for ((request, responseDeferred) in requestChannel) {
val response = processRequest(request)
responseDeferred.complete(response) // 返回给特定请求者
}
}
}
suspend fun sendRequest(request: Request): Response {
val responseDeferred = CompletableDeferred<Response>()
requestChannel.send(request to responseDeferred)
return responseDeferred.await() // 等待特定响应
}
}
// 3. 背压敏感的任务处理
class ImageProcessor {
// 使用 CONFLATED 确保不会堆积太多任务
private val imageChannel = Channel<Image>(Channel.CONFLATED)
init {
launch {
for (image in imageChannel) {
processImage(image) // 只处理最新的图片,跳过中间的
}
}
}
fun processNewImage(image: Image) {
imageChannel.trySend(image) // 如果处理不过来,只保留最新图片
}
}
🔄 互操作性:如何相互转换
虽然不能完全替代,但它们可以相互转换:
kotlin
// Channel → SharedFlow (将队列转为广播)
fun Channel<T>.asSharedFlow(): SharedFlow<T> = produceIn(this).consumeAsFlow()
.shareIn(
scope = CoroutineScope(Dispatchers.Default),
started = SharingStarted.Eagerly,
replay = 0
)
// SharedFlow → Channel (广播转队列,通常不需要)
fun <T> SharedFlow<T>.asChannel(capacity: Int = Channel.RENDEZVOUS): Channel<T> {
val channel = Channel<T>(capacity)
onEach { channel.send(it) }
return channel
}
🎯 选择指南:何时用什么?
选择 SharedFlow 当:
- 多个观察者需要相同数据(状态管理)
- 事件是瞬时通知,不需要确保处理
- 实现发布-订阅模式
- UI 事件总线(但要注意生命周期)
选择 Channel 当:
- 任务需要被精确处理一次(工作队列)
- 需要请求-响应模式(RPC 风格)
- 实现生产者-消费者模式
- 需要背压控制和工作窃取
实战示例:车载空调 App 中的选择
kotlin
class CarAirConditionerApp {
// ✅ 适合 SharedFlow:
// 1. 温度设置状态(多个界面显示同一温度)
private val _temperature = MutableStateFlow(22.0)
val temperature = _temperature.asStateFlow()
// 2. 系统事件通知(如"滤芯需要更换")
private val _systemEvents = MutableSharedFlow<SystemEvent>()
val systemEvents = _systemEvents.asSharedFlow()
// ✅ 适合 Channel:
// 1. CAN 消息处理队列(每条消息只处理一次)
private val canMessageChannel = Channel<CanMessage>(Channel.UNLIMITED)
// 2. 异步控制指令(确保执行且只执行一次)
private val commandChannel = Channel<ControlCommand>()
init {
// Channel 场景:处理 CAN 消息
launch {
for (message in canMessageChannel) {
// 每条消息只由一个处理器处理
processCanMessage(message)
}
}
// SharedFlow 场景:广播状态更新
launch {
temperature.collect { temp ->
// 多个 UI 组件同时更新
dashboardFragment.updateTemperature(temp)
controlPanelFragment.updateTemperature(temp)
hudFragment.updateTemperature(temp)
}
}
}
// 发送 CAN 消息(使用 Channel)
fun receiveCanMessage(message: CanMessage) {
canMessageChannel.trySend(message) // 确保消息进入队列
}
// 发送系统事件(使用 SharedFlow)
fun notifyFilterReplacement() {
_systemEvents.tryEmit(SystemEvent.FILTER_REPLACEMENT_NEEDED)
}
}
💡 总结
- SharedFlow 是 "广播":适合多个消费者需要相同数据的场景
- Channel 是 "队列":适合任务需要被精确处理一次的场景
简单规则:
- 如果问题是 "谁需要知道这个变化?" → 用 SharedFlow
- 如果问题是 "谁应该处理这个任务?" → 用 Channel
在您的车载空调 App 中:
- 用户操作事件、状态更新 → SharedFlow
- CAN 消息处理、控制指令执行 → Channel
两者配合使用,才能构建出既高效又可靠的系统。