Kotlin Flow 的协程边界突破:channelFlow 与 callbackFlow 的实战解析

124 阅读3分钟

一、为什么在普通 Flow 中从另一个协程发射会失败?

1. 普通 Flow 是顺序的、同步的

fun simpleFlow(): Flow<Int> = flow {
    // 正确:在同一个协程中发射
    emit(1)
    
    // 错误:不能在另一个协程中发射
    launch(Dispatchers.IO) {
        // emit(2) // 编译错误:emit 只能在 flow builder 的协程中调用
    }
}

原因:普通 Flow 的 emit() 函数不是线程安全的,且必须在同一个协程上下文中调用。

2. 结构化并发约束

普通 Flow 遵守结构化并发原则:当收集器取消时,整个 flow 构建器块都会被取消。如果允许在外部协程发射,难以追踪和管理所有发射源的取消。

二、ChannelFlow 解决了什么问题?

ChannelFlow 的核心价值:跨协程发射

fun channelFlowExample(): Flow<Int> = channelFlow {
    // 可以在多个协程中并发发射
    launch {
        for (i in 1..5) {
            send(i) // 使用 send 而不是 emit
            delay(100)
        }
    }
    
    launch {
        for (i in 6..10) {
            send(i)
            delay(150)
        }
    }
    
    // 等待所有发射完成
    awaitClose()
}

解决的问题:

1. 并发数据源整合

fun mergeMultipleSources(): Flow<String> = channelFlow {
    // 多个异步数据源
    val sources = listOf(
        async { fetchFromNetwork() },
        async { readFromDatabase() },
        async { getFromCache() }
    )
    
    sources.forEach { deferred ->
        launch {
            val result = deferred.await()
            result.forEach { item ->
                send(item)
            }
        }
    }
}

2. 生产者-消费者模式

fun producerConsumer(): Flow<Data> = channelFlow {
    val channel = this.channel // 获取底层的 Channel
    
    // 生产者协程
    launch(Dispatchers.IO) {
        while (isActive) {
            val data = produceData()
            send(data)
        }
    }
    
    // 另一个生产者
    launch(Dispatchers.Default) {
        processAndSendResults()
    }
}

三、CallbackFlow 解决了什么问题?

CallbackFlow 的特殊用途:桥接回调API

fun listenToLocationUpdates(): Flow<Location> = callbackFlow {
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.locations.forEach { location ->
                trySend(location) // 非阻塞式发送
            }
        }
    }
    
    // 注册回调
    locationClient.requestLocationUpdates(
        request,
        callback,
        Looper.getMainLooper()
    )
    
    // 关键:当流被取消时,自动清理资源
    awaitClose {
        locationClient.removeLocationUpdates(callback)
    }
}

解决的问题:

1. 回调API的Flow化

// 传统回调 → Flow 转换
interface EventListener {
    fun onEvent(event: Event)
    fun onError(error: Throwable)
}

fun listenToEvents(): Flow<Event> = callbackFlow {
    val listener = object : EventListener {
        override fun onEvent(event: Event) {
            trySend(event)
        }
        
        override fun onError(error: Throwable) {
            close(error)
        }
    }
    
    registerListener(listener)
    
    awaitClose {
        unregisterListener(listener)
    }
}

2. 生命周期管理

fun observeAndroidLifecycle(): Flow<Lifecycle.State> = callbackFlow {
    val observer = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_RESUME -> trySend(Lifecycle.State.RESUMED)
            Lifecycle.Event.ON_PAUSE -> trySend(Lifecycle.State.STARTED)
            // ... 其他状态
        }
    }
    
    lifecycle.addObserver(observer)
    
    awaitClose {
        lifecycle.removeObserver(observer)
    }
}

四、底层原理对比

特性普通 FlowChannelFlowCallbackFlow
发射上下文必须相同协程可跨协程可跨协程
线程安全
缓冲区可配置缓冲区可配置缓冲区
适用场景同步/简单异步并发数据源回调API集成
取消传播自动手动管理自动清理

五、实际应用示例

示例1:WebSocket 连接

fun connectWebSocket(url: String): Flow<Message> = callbackFlow {
    val webSocket = WebSocketClient(url, object : WebSocketListener() {
        override fun onMessage(message: String) {
            trySend(Message.Text(message))
        }
        
        override fun onClose(code: Int, reason: String?) {
            close(ClosedException(code, reason))
        }
    })
    
    webSocket.connect()
    
    awaitClose {
        webSocket.disconnect()
    }
}

示例2:传感器数据流

fun sensorDataFlow(sensor: Sensor): Flow<SensorData> = channelFlow {
    val sensorManager = getSensorManager()
    
    // 在主线程注册监听器
    launch(Dispatchers.Main) {
        sensorManager.registerListener(
            { event ->
                launch(Dispatchers.Default) {
                    // 在后台线程处理并发送
                    val processed = processSensorEvent(event)
                    send(processed)
                }
            },
            sensor,
            SensorManager.SENSOR_DELAY_NORMAL
        )
    }
    
    awaitClose {
        sensorManager.unregisterListener()
    }
}

六、关键要点

1. ChannelFlow:

  • 用于需要从多个协程并发发射数据的场景
  • 提供了底层的 Channel 作为缓冲区
  • 需要手动处理并发和完成信号

2. CallbackFlow:

  • 专门用于包装回调式 API
  • 自动处理资源清理(通过 awaitClose
  • 使用 trySend 避免阻塞

3. 为什么需要它们:

  • 打破普通 Flow 的"单协程发射"限制
  • 支持与现有的异步 API(回调、监听器)集成
  • 提供更灵活的生产者-消费者模式
  • 更好的资源管理和生命周期控制

选择哪个取决于具体需求:如果是处理并发数据源,用 channelFlow;如果是包装回调 API,用 callbackFlow