网站建设解说词,以小说名字做网站的小说网,网站改版的方式大致有,嘉兴网站备案去哪里前言 欢迎加入开源鸿蒙跨平台社区#xff1a;https://openharmonycrossplatform.csdn.net 语音识别不只是开始那么简单#xff0c;怎么结束同样重要。flutter_speech提供了两种结束方式——stop和cancel#xff0c;它们的语义完全不同#xff1a;stop是this.isListeningfalse;}result.success(true);}catch(e){console.error(TAG,stop error:${JSON.stringify(e)});result.success(true);}}2.2 逐行解析第1行if (this.asrEngine this.isListening)双重检查引擎存在且正在监听如果引擎不存在或没在监听直接跳过返回success第2行this.asrEngine.finish(this.sessionId)调用Core Speech Kit的finish方法传入sessionId指定要停止的会话引擎会处理剩余音频并触发最终结果回调第3行this.isListening false立即将状态标记为未监听不等回调触发再改状态避免竞态条件第4行result.success(true)无论是否执行了finish都返回success这是因为停止操作本身不应该失败——即使没在监听停止也是合理的2.3 为什么catch中也返回successcatch(e){console.error(TAG,stop error:${JSON.stringify(e)});result.success(true);// ← 错误了也返回success}这是一个设计决策stop操作的语义是请求停止即使底层出了异常从用户的角度来说停止这个动作已经完成了。返回error会让Dart层的Future抛异常可能导致UI出现不必要的错误提示。我的看法这种处理方式有争议。有人认为应该把错误传回去让调用方知道。但在实际使用中stop失败通常是因为引擎已经自己停了比如VAD超时这时候返回error反而会让用户困惑。所以flutter_speech选择了静默成功的策略。2.4 finish后的回调时序调用finish后引擎会触发以下回调asrEngine.finish(sessionId) │ ├── 引擎处理剩余音频可能需要几百毫秒 │ ├── onResult(result, isLasttrue) ← 最终结果 │ ├── speech.onSpeech(text) │ └── speech.onRecognitionComplete(text) │ └── onComplete(sessionId, message) └── (isListening已经是false跳过)⚠️注意finish是异步的——调用后不会立即触发回调。引擎需要时间处理剩余音频。所以result.success(true)是在回调之前返回的Dart层收到success后还需要等待onRecognitionComplete回调才能拿到最终结果。三、cancel 方法立即中断不返回结果3.1 源码实现privatecancel(result:MethodResult):void{try{if(this.asrEnginethis.isListening){this.asrEngine.cancel(this.sessionId);this.isListeningfalse;}result.success(true);}catch(e){console.error(TAG,cancel error:${JSON.stringify(e)});result.success(true);}}3.2 与stop的代码差异把两个方法放在一起对比// stopthis.asrEngine.finish(this.sessionId);// ← 唯一的区别// cancelthis.asrEngine.cancel(this.sessionId);// ← 唯一的区别代码结构完全一样唯一的区别就是调用的API不同finishvscancel。3.3 cancel后的回调行为asrEngine.cancel(sessionId) │ ├── 引擎立即停止不处理剩余音频 │ ├── onComplete(sessionId, message) ← 可能触发 │ └── (isListening已经是false跳过) │ └── 不会触发onResult(isLasttrue) ← 关键区别cancel后不会触发onResult(isLasttrue)所以Dart层不会收到speech.onRecognitionComplete事件。这正是cancel的语义——“不要结果了”。3.4 cancel的使用场景场景说明用户点击取消按钮用户主动放弃本次识别切换语言后重新识别先cancel旧的再start新的页面退出离开语音识别页面时清理防重入startListening中先cancel旧会话四、isListening 状态管理与防重入处理4.1 isListening的生命周期privateisListening:booleanfalse;isListening在以下位置被修改位置操作新值说明startListening开始识别true标记为监听中startListening防重入cancel旧会话false → true先false再truestop停止识别false标记为未监听cancel取消识别false标记为未监听onResult(isLasttrue)收到最终结果false识别自然结束onComplete会话完成false兜底处理onError发生错误false错误恢复4.2 状态转换图startListening false ─────────────────────────────► true ▲ │ │ │ │ stop/cancel │ onResult(isLasttrue) │ onError │ onComplete │ onComplete │ │ │ └────────────────────────────────────┘4.3 防重入的实现在startListening中if(this.isListening){this.asrEngine.cancel(this.sessionId);this.isListeningfalse;}这段代码确保了同一时间只有一个活跃的识别会话。如果用户快速连续点击开始按钮不会出现多个会话冲突。用户快速点击两次开始 第1次点击 isListening false → startListening → isListening true 第2次点击第1次还在识别中 isListening true → cancel第1次 → isListening false → startListening → isListening true4.4 竞态条件分析有一个潜在的竞态条件stop方法将isListening设为false但onResult(isLasttrue)回调可能在之后触发也会将isListening设为false。// stop方法this.asrEngine.finish(this.sessionId);this.isListeningfalse;// ← 第1次设false// 稍后onResult回调触发onResult(sessionId,result){if(result.isLast){plugin.isListeningfalse;// ← 第2次设false重复但无害}}这种重复设置是无害的——false设两次还是false。flutter_speech的设计选择了宁可重复也不遗漏的策略。五、边界场景处理重复调用、引擎未初始化5.1 边界场景清单场景stop的行为cancel的行为正常识别中finish 返回结果cancel 不返回结果未在识别isListeningfalse跳过finish返回success跳过cancel返回success引擎未初始化asrEnginenull跳过返回success跳过返回success连续调用两次stop第1次正常第2次跳过第1次正常第2次跳过stop后立即cancelstop正常cancel跳过-cancel后立即stopcancel正常stop跳过-5.2 引擎未初始化if(this.asrEnginethis.isListening){// 只有引擎存在且正在监听时才执行}result.success(true);// 无论如何都返回success如果用户在没有调用activate的情况下直接调用stop或cancelasrEngine为null条件不满足直接返回success。不会报错也不会崩溃。5.3 连续调用stop() → isListening false stop() → isListening已经是false → 跳过finish → 返回success第二次调用时isListening已经是false不会重复调用finish。这是安全的。5.4 stop和cancel交叉调用stop() → finish isListening false cancel() → isListening是false → 跳过cancel → 返回success先stop后cancelcancel会被跳过。反过来也一样。这是正确的行为——已经停止了就不需要再取消。六、stop/cancel 与 Dart 层的交互6.1 Dart层的调用// 停止识别Futurestop()_channel.invokeMethod(speech.stop);// 取消识别Futurecancel()_channel.invokeMethod(speech.cancel);6.2 示例App中的使用// 停止按钮ElevatedButton(onPressed:_isListening?()_speech.stop():null,child:Text(Stop),),// 取消按钮ElevatedButton(onPressed:_isListening?()_speech.cancel():null,child:Text(Cancel),),注意按钮的onPressed只在_isListening为true时才可点击。这是UI层的防重入——如果没在识别按钮是灰色的。6.3 stop后的结果获取stop后Dart层通过recognitionCompleteHandler回调获取最终结果_speech.setRecognitionCompleteHandler((Stringtext){setState((){_transcriptiontext;_isListeningfalse;});});cancel后recognitionCompleteHandler不会被调用所以_transcription保持之前的值部分结果或空字符串。6.4 完整的交互时序stop场景Dart: _speech.stop() → Native: onMethodCall(speech.stop) → Native: asrEngine.finish(sessionId) → Native: result.success(true) → Dart: stop() Future完成 (稍后) → Native: onResult(text, isLasttrue) → Native: channel.invokeMethod(speech.onSpeech, text) → Native: channel.invokeMethod(speech.onRecognitionComplete, text) → Dart: recognitionResultHandler(text) → Dart: recognitionCompleteHandler(text)cancel场景Dart: _speech.cancel() → Native: onMethodCall(speech.cancel) → Native: asrEngine.cancel(sessionId) → Native: result.success(true) → Dart: cancel() Future完成 (不会有后续回调)七、与Android实现的对比7.1 代码对比Android stopprivatevoidstopListening(MethodChannel.Resultresult){try{if(speechRecognizer!nullisListening){speechRecognizer.stopListening();isListeningfalse;}result.success(true);}catch(Exceptione){result.success(true);}}OpenHarmony stopprivatestop(result:MethodResult):void{try{if(this.asrEnginethis.isListening){this.asrEngine.finish(this.sessionId);this.isListeningfalse;}result.success(true);}catch(e){console.error(TAG,stop error:${JSON.stringify(e)});result.success(true);}}7.2 差异点差异AndroidOpenHarmonyAPI名称stopListening()finish(sessionId)需要sessionId❌ 不需要✅ 需要错误日志无有console.error代码结构几乎一样几乎一样两个平台的实现高度相似这说明flutter_speech的适配做得很好——保持了跨平台的一致性。八、最佳实践与注意事项8.1 何时用stop何时用cancel场景推荐操作原因用户说完了想要结果stop需要最终识别结果用户想重新说cancel不需要当前结果切换语言cancel旧语言的结果没用页面退出cancel不需要结果了超时处理stop尽量保留已识别的内容错误恢复cancel清理状态重新开始8.2 注意事项不要在stop后立即startListeningfinish是异步的需要等onComplete回调后再开始新的识别cancel后可以立即startListeningcancel是立即生效的不要忘记更新UI状态stop/cancel后要更新按钮状态destroyEngine前先stop/cancel确保识别已停止再销毁引擎// ✅ 正确的销毁顺序if(this.isListening){this.asrEngine.cancel(this.sessionId);// 先停止}this.asrEngine.shutdown();// 再销毁总结本文详细讲解了flutter_speech中语音识别的停止与取消语义区别stop(finish)返回最终结果cancel丢弃结果实现结构两个方法代码几乎一样只是调用的API不同状态管理通过isListening标志防止重复操作和竞态条件边界处理引擎未初始化、未在监听、重复调用都能安全处理错误策略异常时也返回success避免不必要的错误提示下一篇我们讲引擎销毁与资源释放——destroyEngine方法的实现和资源管理的最佳实践。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源Core Speech Kit finish/cancel文档Android SpeechRecognizer.stopListeningAndroid SpeechRecognizer.cancelflutter_speech OpenHarmony源码状态机设计模式Flutter Platform Channel通信开源鸿蒙跨平台社区ArkTS异步编程指南