4. EventChannel与流式通信
MethodChannel 解决的是“Dart 主动调用原生、原生返回一次结果”的请求—响应模型。但很多场景里,数据的发起方在原生侧:传感器持续上报、蓝牙连接状态变化、下载进度更新、定位坐标流、播放器缓冲状态、原生 SDK 的异步回调。这些数据不是 Dart 问一次才给一次,而是原生在任意时刻主动推送。用 MethodChannel 硬做,只能靠 Dart 定时轮询,既浪费资源又丢失实时性。EventChannel 正是为这种“原生主动、Dart 被动接收”的流式通信设计的。
EventChannel 的通信模型
EventChannel 建立在 MethodChannel 之上,但方向相反。它把原生侧的一个事件源包装成 Dart 的 Stream:Dart 端调用 receiveBroadcastStream() 得到一个流对象,随后通过 listen 注册监听;原生端在 onListen 回调里拿到一个 EventSink,只要调用 sink.success(data),数据就会沿着平台通道推送到 Dart 的监听回调里。当 Dart 取消订阅时,原生端收到 onCancel 回调,应当在此停止数据源、释放资源。
理解它的关键是把“订阅生命周期”和“数据推送”分开看:onListen / onCancel 管的是订阅的建立与销毁,EventSink 管的是数据往 Dart 送。二者配合,才能做到“有人听才推、没人听就停”。
原生端实现:onListen 与 onCancel
以 Android 为例,EventChannel 需要一个 StreamHandler。它的 onListen 收到 EventSink 后,通常把它保存为成员变量,并启动真正的数据源,比如注册传感器监听、订阅原生 SDK 的回调。数据到来时调用 sink.success(...)。当 Dart 取消订阅,onCancel 被触发,此时必须注销监听、关闭定时器或取消原生订阅,否则会持续占用资源甚至向已销毁的 sink 推送导致异常。
class StepHandler : StreamHandler {
private var sink: EventSink? = null
private val timer = Timer()
override fun onListen(args: Any?, events: EventSink) {
sink = events
timer.schedule(object : TimerTask() {
override fun run() {
val step = readStepCount() // 读取真实步数
sink?.success(step) // 推送到 Dart
}
}, 0, 1000)
}
override fun onCancel(args: Any?) {
timer.cancel() // 停止数据源
sink = null // 释放句柄
}
}
iOS 侧对应实现 FlutterStreamHandler 的 onListen(withArguments:eventSink:) 与 onCancel(withArguments:),逻辑一致:onListen 保存 sink 并启动数据源,onCancel 停止并置空。注意 EventSink 的调用必须在主线程,若数据源在子线程产生,需要切回主线程再 success。
Dart 端监听与取消订阅
Dart 端通过 EventChannel('channel_name') 创建通道,调用 receiveBroadcastStream() 得到广播流。广播流允许多个监听者,但每个监听者都会触发一次原生 onListen,因此原生端要能处理多次订阅。典型用法如下:
final _channel = EventChannel('com.example/step_events');
StreamSubscription? _sub;
void startListening() {
_sub = _channel.receiveBroadcastStream().listen(
(event) {
setState(() => _steps = event as int);
},
onError: (error) {
debugPrint('事件流错误: $error');
},
cancelOnError: false,
);
}
void stopListening() {
_sub?.cancel(); // 触发原生 onCancel
_sub = null;
}
这里有几个容易踩的点。第一,receiveBroadcastStream 的参数会作为 arguments 传给原生 onListen,可用于传递采样频率、设备 ID 等配置。第二,cancelOnError 默认为 false,若设为 true,一旦原生 sink.error 推送错误,订阅会自动取消,后续数据不再到达。第三,取消订阅后必须把 _sub 置空,避免重复 cancel 或误用已取消的订阅。第四,在 StatefulWidget 的 dispose 中一定要取消订阅,否则页面销毁后原生仍在推送,造成内存泄漏。
错误处理与结束信号
EventChannel 除了 success,还支持 sink.error(code, message, details) 推送错误,Dart 端在 listen 的 onError 中接收。当数据源自然结束时,原生调用 sink.endOfStream(),Dart 端收到 onDone 回调,表示流正常关闭。区分“错误”和“正常结束”很重要:错误通常意味着需要重试或提示用户,而 endOfStream 表示任务完成,不应再期待数据。
与 MethodChannel 的选型对比
| 维度 | MethodChannel | EventChannel |
|---|---|---|
| 发起方 | Dart 主动调用 | 原生主动推送 |
| 返回次数 | 一次结果 | 多次事件,持续流 |
| Dart 侧形态 | Future | Stream |
| 典型场景 | 读取配置、执行命令 | 传感器、进度、状态变化 |
| 生命周期 | 调用即结束 | 需显式取消订阅 |
实际工程中,二者常组合使用:用 MethodChannel 下发“开始采集”“设置采样率”等命令,用 EventChannel 接收采集到的数据流。这样职责清晰,也便于分别测试。
实践要点小结
- 原生
onListen中启动数据源并保存EventSink,onCancel中务必停止数据源并置空 sink。 - 推送数据前判断 sink 是否为空,避免向已取消的订阅发送数据。
- Dart 端在
dispose中取消订阅,防止页面销毁后原生继续推送。 - 用
sink.error传递异常,用sink.endOfStream表示正常结束,Dart 端分别用onError和onDone处理。 - 需要双向交互时,EventChannel 负责下行数据,MethodChannel 负责上行控制。
掌握 EventChannel 后,原生侧任何“会持续变化的状态”都可以被安全、实时地映射为 Dart 的 Stream,配合 StreamBuilder 即可直接驱动 UI 刷新,这是 Flutter 插件处理实时数据流的标准做法。