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 送。二者配合,才能做到“有人听才推、没人听就停”。

EventChannel 订阅生命周期与数据推送 Dart 层 receiveBroadcastStream() 获得 Stream 对象 stream.listen(onData) 注册监听回调 onData(event) 接收原生推送数据 subscription.cancel() 取消订阅 原生层 onListen(args, sink) 启动数据源 EventSink 持有推送句柄 sink.success(data) 主动推送事件 onCancel(args) 停止数据源、释放资源 ① 建立订阅(listen) ② 返回 EventSink 句柄 ③ 原生主动推送数据(可多次) ④ 取消订阅(cancel) 订阅建立后,数据推送可发生任意多次;取消订阅后原生必须停止推送

原生端实现: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 表示任务完成,不应再期待数据。

EventChannel 只负责“原生推、Dart 收”的单向流。如果 Dart 还需要向原生发送控制指令(如开始/暂停采集),应另建一个 MethodChannel 配合使用,而不是试图在 EventChannel 上做双向通信。

与 MethodChannel 的选型对比

维度MethodChannelEventChannel
发起方Dart 主动调用原生主动推送
返回次数一次结果多次事件,持续流
Dart 侧形态FutureStream
典型场景读取配置、执行命令传感器、进度、状态变化
生命周期调用即结束需显式取消订阅

实际工程中,二者常组合使用:用 MethodChannel 下发“开始采集”“设置采样率”等命令,用 EventChannel 接收采集到的数据流。这样职责清晰,也便于分别测试。

实践要点小结

  • 原生 onListen 中启动数据源并保存 EventSink,onCancel 中务必停止数据源并置空 sink。
  • 推送数据前判断 sink 是否为空,避免向已取消的订阅发送数据。
  • Dart 端在 dispose 中取消订阅,防止页面销毁后原生继续推送。
  • 用 sink.error 传递异常,用 sink.endOfStream 表示正常结束,Dart 端分别用 onError 和 onDone 处理。
  • 需要双向交互时,EventChannel 负责下行数据,MethodChannel 负责上行控制。

掌握 EventChannel 后,原生侧任何“会持续变化的状态”都可以被安全、实时地映射为 Dart 的 Stream,配合 StreamBuilder 即可直接驱动 UI 刷新,这是 Flutter 插件处理实时数据流的标准做法。