前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前面几篇我们分别讲了权限申请、引擎创建、监听器、启动停止、资源释放。这些功能散落在不同的方法中,是时候把它们串起来了。

MethodChannel是连接Dart层和原生层的桥梁,所有的交互都通过它完成。flutter_speech的通信是双向的——Dart层可以调用原生方法(如activate、listen),原生层也可以主动通知Dart层(如识别结果、错误事件)。

我在适配过程中发现,理解通信的全貌比理解单个方法更重要。很多bug不是某个方法写错了,而是通信时序搞混了——比如在引擎还没创建好的时候就发了onSpeechAvailability,或者在channel已经被注销后还在invokeMethod。

今天我们把flutter_speech中所有的通信路径都梳理一遍,画一张完整的通信地图。

💡 本文重点:不是讲某个方法的实现细节(前面已经讲过了),而是从通信协议的角度把所有交互串起来。
请添加图片描述

一、Dart → Native:onMethodCall 方法分发

1.1 通信方向

Dart层                          Native层(OpenHarmony)
──────                          ────────────────────
_channel.invokeMethod(...)  →   onMethodCall(call, result)
                            ←   result.success / result.error

Dart层通过invokeMethod发起调用,Native层通过result返回结果。这是一个请求-响应模式。

1.2 flutter_speech中的所有Dart→Native调用

Dart方法 Channel方法名 参数 返回值 对应Native方法
activate(locale) “speech.activate” String locale bool activate()
listen() “speech.listen” bool startListening()
cancel() “speech.cancel” bool cancel()
stop() “speech.stop” bool stop()
(预留) “speech.destroy” bool destroyEngine()

1.3 Dart层的调用代码

class SpeechRecognition {
  static const MethodChannel _channel =
      const MethodChannel('com.flutter.speech_recognition');

  Future activate(String locale) =>
      _channel.invokeMethod("speech.activate", locale);

  Future listen() =>
      _channel.invokeMethod("speech.listen");

  Future cancel() =>
      _channel.invokeMethod("speech.cancel");

  Future stop() =>
      _channel.invokeMethod("speech.stop");
}

1.4 Native层的分发代码

onMethodCall(call: MethodCall, result: MethodResult): void {
  switch (call.method) {
    case "speech.activate":
      this.activate(String(call.args), result).catch((e: Error) => {
        console.error(TAG, `activate unhandled error: ${e.message}`);
        result.error('SPEECH_ACTIVATION_ERROR', e.message, null);
      });
      break;
    case "speech.listen":
      this.startListening(result);
      break;
    case "speech.cancel":
      this.cancel(result);
      break;
    case "speech.stop":
      this.stop(result);
      break;
    case "speech.destroy":
      this.destroyEngine();
      result.success(true);
      break;
    default:
      result.notImplemented();
      break;
  }
}

1.5 参数传递方式

方法 Dart传参方式 Native取参方式 参数类型
activate invokeMethod(“speech.activate”, locale) String(call.args) String
listen invokeMethod(“speech.listen”) 无参数 -
cancel invokeMethod(“speech.cancel”) 无参数 -
stop invokeMethod(“speech.stop”) 无参数 -

flutter_speech的参数传递非常简单——只有activate需要传一个locale字符串,其他方法都不需要参数。

📌 注意:Dart层用call.arguments(复数),OpenHarmony用call.args(缩写)。这个命名差异在第8篇已经提过,这里再强调一下。

1.6 结果返回方式

每个方法都必须通过result对象返回结果:

// 成功
result.success(true);

// 错误
result.error('ERROR_CODE', 'Error message', null);

// 未实现
result.notImplemented();

Dart层的Future会根据返回方式不同而有不同的表现:

Native返回 Dart Future行为 Dart代码
result.success(value) 正常完成,值为value .then((res) => …)
result.error(code, msg, details) 抛出PlatformException .catchError((e) => …)
result.notImplemented() 抛出MissingPluginException .catchError((e) => …)

二、Native → Dart:channel.invokeMethod 回调通知

2.1 通信方向

Native层(OpenHarmony)          Dart层
────────────────────          ──────
channel.invokeMethod(...)  →  _platformCallHandler(call)

Native层通过channel.invokeMethod主动向Dart层发送事件。这是一个推送模式,不需要Dart层先发起请求。

2.2 flutter_speech中的所有Native→Dart事件

Native调用 Channel方法名 参数 触发时机 Dart回调
引擎就绪 “speech.onSpeechAvailability” bool activate成功/失败 availabilityHandler
识别开始 “speech.onRecognitionStarted” null onStart回调 recognitionStartedHandler
识别结果 “speech.onSpeech” String onResult回调 recognitionResultHandler
识别完成 “speech.onRecognitionComplete” String onResult(isLast=true) recognitionCompleteHandler
错误 “speech.onError” int onError回调 errorHandler

2.3 Native层的发送代码

// 1. 引擎可用性通知
this.channel?.invokeMethod('speech.onSpeechAvailability', true);
this.channel?.invokeMethod('speech.onSpeechAvailability', false);

// 2. 识别开始通知
channel?.invokeMethod('speech.onRecognitionStarted', null);

// 3. 实时识别结果
channel?.invokeMethod('speech.onSpeech', result.result);

// 4. 识别完成
channel?.invokeMethod('speech.onRecognitionComplete', result.result);

// 5. 错误通知
channel?.invokeMethod('speech.onError', errorCode);

2.4 Dart层的接收代码

SpeechRecognition._internal() {
  _channel.setMethodCallHandler(_platformCallHandler);
}

Future _platformCallHandler(MethodCall call) async {
  switch (call.method) {
    case "speech.onSpeechAvailability":
      availabilityHandler(call.arguments);
      break;
    case "speech.onSpeech":
      recognitionResultHandler(call.arguments);
      break;
    case "speech.onRecognitionStarted":
      recognitionStartedHandler();
      break;
    case "speech.onRecognitionComplete":
      recognitionCompleteHandler(call.arguments);
      break;
    case "speech.onError":
      errorHandler();
      break;
  }
}

2.5 可选链操作符的使用

注意Native层发送事件时使用了?.(可选链操作符):

channel?.invokeMethod('speech.onSpeech', result.result);
//     ↑ 可选链

这是因为channel可能为null(在onDetachedFromEngine之后)。使用?.可以安全地跳过null的情况,不会抛异常。

💡 防御性编程:在回调中使用channel时,总是用?.而不是.。因为回调可能在引擎销毁后才触发(异步时序问题),此时channel可能已经是null了。

三、speech.onSpeechAvailability 事件传递

3.1 事件含义

speech.onSpeechAvailability告诉Dart层"语音识别是否可用"。

3.2 触发场景

场景 参数值 含义
activate成功 true 引擎就绪,可以开始识别
onError触发 false 引擎出错,不可用

3.3 发送位置

// 位置1:activate成功后
this.channel?.invokeMethod('speech.onSpeechAvailability', true);

// 位置2:onError回调中
channel?.invokeMethod('speech.onSpeechAvailability', false);

3.4 Dart层的处理

_speech.setAvailabilityHandler((bool result) {
  setState(() => _speechRecognitionAvailable = result);
});

UI根据_speechRecognitionAvailable的值来控制按钮的可用状态:

ElevatedButton(
  onPressed: _speechRecognitionAvailable && !_isListening
      ? () => _speech.listen()
      : null,  // 不可用时按钮灰色
  child: Text('Listen'),
)

3.5 时序分析

activate("zh_CN")
    │
    ├── 权限申请 → 成功
    ├── 能力检测 → 支持
    ├── 引擎创建 → 成功
    ├── 设置监听器
    │
    ├── invokeMethod('speech.onSpeechAvailability', true)  ← 通知可用
    │   → Dart: availabilityHandler(true)
    │   → UI: "Listen"按钮变为可点击
    │
    └── result.success(true)
        → Dart: activate() Future完成

📌 注意时序onSpeechAvailability(true)是在result.success(true)之前发送的。这意味着Dart层可能先收到可用性通知,再收到activate的返回值。不过在实际使用中,这个顺序差异不影响功能。

四、speech.onSpeech 实时识别结果推送

4.1 事件含义

speech.onSpeech推送实时的识别文本,每次识别引擎产生新的结果都会触发。

4.2 触发频率

一次识别过程中,speech.onSpeech会被触发多次

用户说"打开设置页面":

t=0.3s: speech.onSpeech("打")
t=0.5s: speech.onSpeech("打开")
t=0.8s: speech.onSpeech("打开设置")
t=1.2s: speech.onSpeech("打开设置页面")
t=1.5s: speech.onSpeech("打开设置页面")  ← 最终结果(同时触发onRecognitionComplete)

4.3 参数类型

// Native端发送
channel?.invokeMethod('speech.onSpeech', result.result);
// result.result 是 string 类型
// Dart端接收
case "speech.onSpeech":
  recognitionResultHandler(call.arguments);
  // call.arguments 是 String 类型

4.4 UI更新

_speech.setRecognitionResultHandler((String text) {
  setState(() {
    _transcription = text;  // 更新显示的文本
  });
});

每次收到新的结果,UI都会更新显示。用户能看到文字一个个蹦出来的效果。

五、speech.onRecognitionComplete 完成事件处理

5.1 事件含义

speech.onRecognitionComplete表示识别已完成,携带最终的识别文本。

5.2 触发位置

这个事件可能从两个地方发出:

// 位置1:onResult回调中(isLast=true时)
onResult(sessionId, result) {
  if (result.isLast) {
    plugin.isListening = false;
    channel?.invokeMethod('speech.onRecognitionComplete', result.result);
  }
}

// 位置2:onComplete回调中(兜底)
onComplete(sessionId, eventMessage) {
  if (plugin.isListening) {
    plugin.isListening = false;
    channel?.invokeMethod('speech.onRecognitionComplete', plugin.lastTranscription);
  }
}

通过isListening标志保证只发送一次。

5.3 与speech.onSpeech的区别

维度 speech.onSpeech speech.onRecognitionComplete
触发次数 多次 1次
含义 实时部分结果 最终完整结果
用途 更新UI显示 标记识别结束
参数 当前文本 最终文本

5.4 Dart层的处理

_speech.setRecognitionCompleteHandler((String text) {
  setState(() {
    _transcription = text;
    _isListening = false;  // 标记识别结束
  });
});

收到完成事件后,UI会:

  1. 显示最终文本
  2. 将"正在监听"状态改为"已停止"
  3. 按钮状态恢复

六、完整通信协议图

6.1 一次完整识别的通信时序

Dart层                                    Native层
──────                                    ──────

1. activate("zh_CN") ──────────────────►  onMethodCall("speech.activate", "zh_CN")
                                          │ 权限申请...
                                          │ 引擎创建...
                     ◄──────────────────  invokeMethod("speech.onSpeechAvailability", true)
                     ◄──────────────────  result.success(true)

2. listen() ───────────────────────────►  onMethodCall("speech.listen")
                                          │ startListening...
                     ◄──────────────────  result.success(true)
                                          │
                                          │ (引擎开始采集音频)
                     ◄──────────────────  invokeMethod("speech.onRecognitionStarted")
                                          │
                                          │ (用户说话中...)
                     ◄──────────────────  invokeMethod("speech.onSpeech", "你好")
                     ◄──────────────────  invokeMethod("speech.onSpeech", "你好世界")
                                          │
                                          │ (用户停止说话,VAD超时)
                     ◄──────────────────  invokeMethod("speech.onSpeech", "你好世界")
                     ◄──────────────────  invokeMethod("speech.onRecognitionComplete", "你好世界")

3. (识别自然结束,无需手动stop)

6.2 用户手动stop的时序

Dart层                                    Native层
──────                                    ──────

(识别进行中...)
                     ◄──────────────────  invokeMethod("speech.onSpeech", "今天天气")

stop() ────────────────────────────────►  onMethodCall("speech.stop")
                                          │ finish(sessionId)
                     ◄──────────────────  result.success(true)
                                          │
                                          │ (引擎处理剩余音频)
                     ◄──────────────────  invokeMethod("speech.onSpeech", "今天天气怎么样")
                     ◄──────────────────  invokeMethod("speech.onRecognitionComplete", "今天天气怎么样")

6.3 错误场景的时序

Dart层                                    Native层
──────                                    ──────

activate("en_US") ─────────────────────►  onMethodCall("speech.activate", "en_US")
                                          │ 语言校验失败
                     ◄──────────────────  result.error("ERROR_LANGUAGE_NOT_SUPPORTED", ...)
Dart层                                    Native层
──────                                    ──────

(识别进行中,网络断开)
                                          │ onError(1, "network timeout")
                     ◄──────────────────  invokeMethod("speech.onSpeechAvailability", false)
                     ◄──────────────────  invokeMethod("speech.onError", 1)

七、通信安全性保障

7.1 Channel名称一致性

// Dart层
static const MethodChannel _channel =
    const MethodChannel('com.flutter.speech_recognition');
// Native层
this.channel = new MethodChannel(
  binding.getBinaryMessenger(),
  "com.flutter.speech_recognition"  // 必须完全一致
);

如果两端的Channel名称不一致,所有通信都会失效,但不会报错——方法调用会触发MissingPluginException

7.2 方法名一致性

所有方法名都以speech.为前缀,形成了一个简单的命名空间:

Dart → Native:
  speech.activate
  speech.listen
  speech.cancel
  speech.stop
  speech.destroy

Native → Dart:
  speech.onSpeechAvailability
  speech.onRecognitionStarted
  speech.onSpeech
  speech.onRecognitionComplete
  speech.onError

📌 命名规范:Dart→Native的方法名用动词(activate、listen),Native→Dart的事件名用on前缀(onSpeech、onError)。这种命名方式让通信方向一目了然。

7.3 空值安全

// Native端:channel可能为null
channel?.invokeMethod('speech.onSpeech', result.result);

// Native端:asrEngine可能为null
if (!this.asrEngine) {
  result.error('ERROR_ENGINE_NOT_INITIALIZED', ...);
  return;
}
// Dart端:回调可能未设置(late变量)
case "speech.onSpeech":
  recognitionResultHandler(call.arguments);  // 如果未设置会抛异常

⚠️ Dart层的潜在问题recognitionResultHandlerlate声明的,如果在设置回调之前收到了事件,会抛出LateInitializationError。这是flutter_speech的一个已知限制——必须在调用activate之前设置好所有回调。

7.4 类型安全

事件 Native发送类型 Dart接收类型 是否匹配
onSpeechAvailability boolean dynamic → bool
onRecognitionStarted null 不使用参数
onSpeech string dynamic → String
onRecognitionComplete string dynamic → String
onError number dynamic → int

Platform Channel会自动处理基本类型的序列化和反序列化,所以类型匹配通常不是问题。

八、通信调试技巧

8.1 日志追踪

在关键通信节点添加日志:

// Native端发送事件时
console.info(TAG, `>>> invokeMethod: speech.onSpeech, args=${result.result}`);
channel?.invokeMethod('speech.onSpeech', result.result);

// Native端收到调用时
onMethodCall(call: MethodCall, result: MethodResult): void {
  console.info(TAG, `<<< onMethodCall: ${call.method}, args=${call.args}`);
  // ...
}
# 查看所有通信日志
hdc hilog | grep "FlutterSpeechPlugin" | grep -E "invokeMethod|onMethodCall"

8.2 常见通信问题

问题 症状 原因 解决
Dart调用无响应 Future不完成 某个分支没有调用result 检查所有switch分支
事件收不到 Dart回调不触发 channel为null或Handler未注册 检查生命周期
类型错误 运行时异常 参数类型不匹配 检查发送和接收的类型
重复事件 回调触发多次 监听器注册了多次 确保setupListener只调用一次
事件顺序错乱 UI状态不一致 异步时序问题 用isListening等标志做状态保护

8.3 通信协议检查清单

  • Channel名称两端完全一致
  • 所有Dart→Native方法都有对应的switch分支
  • 所有switch分支都有result响应
  • 所有Native→Dart事件都有对应的_platformCallHandler分支
  • Native端发送事件时使用?.操作符
  • Dart端在使用前设置了所有回调Handler
  • 参数类型两端匹配

总结

本文从通信协议的角度完整梳理了flutter_speech的MethodChannel双向通信:

  1. Dart→Native:5个方法调用(activate、listen、cancel、stop、destroy)
  2. Native→Dart:5个事件通知(onSpeechAvailability、onRecognitionStarted、onSpeech、onRecognitionComplete、onError)
  3. 通信安全:Channel名称一致、方法名一致、空值安全、类型安全
  4. 完整时序:从activate到识别完成的所有通信步骤

下一篇我们讲语言支持与Locale处理——OpenHarmony的语言限制和flutter_speech的应对策略。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐