前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
上一篇搞定了权限申请,今天来讲语音识别引擎的创建——speechRecognizer.createEngine。
这是整个语音识别流程中最关键的一步,引擎创建成功了,后面的监听、识别、停止都是顺水推舟的事。
说实话,createEngine这个API看起来很简单——就两个参数嘛。
但实际用起来,参数格式、异步处理、异常捕获、能力检测,每一个环节都有讲究。
我在适配过程中,光是language参数的格式转换就折腾了好一会儿——Dart层传过来的是zh_CN(下划线),Core
Speech
Kit要求的是zh-CN(连字符),差一个符号引擎就创建失败。
今天把activate方法中引擎创建相关的所有细节都讲透。
💡本文对应源码:
FlutterSpeechPlugin.ets的activate方法,第80-131行。
一、speechRecognizer.createEngine参数详解
1.1
API签名
speechRecognizer.createEngine(params:CreateEngineParams):Promise<SpeechRecognitionEngine>这是一个异步方法,返回Promise。
必须用await等待引擎创建完成。
/>1.2
参数
interfaceCreateEngineParams{language:string;//识别语言,BCP
47格式
online:number;//识别模式:1=在线,0=离线
}flutter_speech中的调用:
this.asrEngine=awaitspeechRecognizer.createEngine({language:language,//"zh-CN"
online:1//在线识别
});| 参数 | 类型 | 必填 | 说明 | flutter_speech的值 |
|---|---|---|---|---|
| language | string | ✅ | BCP 47语言代码 | 由Dart层传入,经convertLocale转换 |
| online | number | ✅ | 1=在线,0=离线 | 固定为1 |
1.3
为什么是异步的
Android的SpeechRecognizer.createSpeechRecognizer()是同步的,为什么OpenHarmony的createEngine是异步的?
原因是Core
Speech
Kit在创建引擎时需要做一些耗时操作:
- 检查系统AI服务是否可用
- 加载语音识别模型
- 建立与AI引擎服务的连接
- 初始化音频采集管道
如果这些操作是同步的,会阻塞主线程,导致UI卡顿。
所以设计成异步是合理的。
//正确:用await等待
this.asrEngine=awaitspeechRecognizer.createEngine({...});console.info(TAG,'enginecreated'
);//引擎已就绪
//错误:不等待直接使用
speechRecognizer.createEngine({...});//返回Promise,引擎还没创建好
this.asrEngine.startListening(...);//asrEngine是undefined,崩溃!
📌这也是为什么
activate方法是async的:因为内部需要await两个异步操作——权限申请和引擎创建。
二、language参数格式转换:locale
问题背景
Dart层传过来的locale格式是下划线分隔的(如zh_CN),这是Dart/Flutter的标准格式。
但Core
Speech
47格式(连字符分隔,如zh-CN)。
Dart层:"zh_CN"
"fr-FR"
2.2convertLocale
实现
flutter_speech的转换逻辑非常简洁:
privateconvertLocale(locale:string):string{returnlocale.replace('_','-');}就一行代码,把下划线替换成连字符。
2.3
为什么不用更复杂的转换
你可能会想:是不是应该做更完整的BCP
47解析?比如处理zh-Hans-CN这种三段式格式?
实际上不需要。
flutter_speech的Dart层只传两段式的locale(如zh_CN、en_US),而且Core
Speech
Kit目前只支持中文,所以简单的replace就够了。
但如果你在做一个更通用的插件,可能需要更健壮的转换:
//更健壮的转换(flutter_speech不需要这么复杂)
privateconvertLocale(locale:string):string{//"zh_CN"
"zh-Hans-CN"
returnlocale.replace(/_/g,'-');//全局替换所有下划线
}💡注意:flutter_speech用的是
replace('_','-')(不带
g标志),只替换第一个下划线。对于两段式locale来说足够了。
如果有三段式locale,需要用正则的全局替换
replace(/_/g,'-')。
2.4
locale转换的调用位置
//activate方法中的调用链
constlanguage=this.convertLocale(locale);//"zh_CN"
"zh-CN"
if(!this.isSupportedLocale(language)){//语言不支持,返回错误
result.error('ERROR_LANGUAGE_NOT_SUPPORTED',...);return;}//用转换后的language创建引擎
this.asrEngine=awaitspeechRecognizer.createEngine({language:language,//"zh-CN"
online:1});三、online
参数含义
| 值 | 模式 | 网络要求 | 准确率 | 延迟 |
|---|---|---|---|---|
| 1 | 在线识别 | 需要网络 | 高 | 取决于网络 |
| 0 | 离线识别 | 不需要 | 中等 | 较低 |
3.2
flutter_speech的选择
flutter_speech硬编码了online:
1(在线模式):
this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:1//固定在线模式
});为什么选在线模式:
- 准确率更高:在线模式使用云端大模型,识别准确率明显优于离线
- 词汇量更大:云端模型的词汇覆盖面更广
- 大多数场景有网络:手机用户通常都有网络连接
3.3
未来改进方向
如果要支持用户选择在线/离线模式,可以通过Dart层传参:
//Dart层(未来可能的改进)
Futureactivate(Stringlocale,{boolonline=true})=>_channel.invokeMethod("speech.activate",{'locale':locale,'online':online,});
//原生端接收参数
case"speech.activate":constargs=call.argsasRecord<string,Object>;constlocale=String(args['locale']);constonline=args['online']asboolean;this.activate(locale,online?1:0,result);break;📌当前flutter_speech的Dart层只传一个locale字符串,所以原生端用
String(call.args)直接获取。如果要传多个参数,需要改成Map格式。
四、SystemCapability.AI.SpeechRecognizer能力检测
4.1
为什么需要能力检测
不是所有OpenHarmony设备都支持语音识别。
在创建引擎之前,应该先检测设备是否具备这个能力:
if(!canIUse('SystemCapability.AI.SpeechRecognizer')){result.error('ERROR_NO_SPEECH_RECOGNITION_AVAILABLE','Devicedoes
recognition'
,null);return;}4.2canIUse
API
canIUse是OpenHarmony的全局函数,用于检测系统能力:
functioncanIUse(syscap:string):boolean| 参数 | 说明 | 示例 |
|---|---|---|
| syscap | 系统能力标识 | “SystemCapability.AI.SpeechRecognizer” |
返回true表示设备支持该能力,false表示不支持。
4.3
哪些设备可能不支持
| 设备类型 | 是否支持语音识别 | 原因 |
|---|---|---|
| 手机(旗舰) | ✅ 通常支持 | 有AI芯片和麦克风 |
| 手机(入门) | ⚠️ 可能不支持 | 硬件能力不足 |
| 平板 | ✅ 通常支持 | 和手机类似 |
| 智慧屏 | ⚠️ 取决于型号 | 部分型号无麦克风 |
| 穿戴设备 | ❌ 通常不支持 | 算力和存储不足 |
| 开发板 | ❌ 通常不支持 | 无AI服务 |
4.4
能力检测的位置
flutter_speech把能力检测放在权限申请之后、引擎创建之前:
//权限申请
//在这里
if(!canIUse('SystemCapability.AI.SpeechRecognizer')){result.error('ERROR_NO_SPEECH_RECOGNITION_AVAILABLE',...);return;}//语言校验
//...
为什么不把能力检测放在最前面?因为即使设备支持语音识别,没有权限也用不了。
先检查权限可以更早地给用户反馈。
💡不过这个顺序见仁见智。
有人觉得应该先检测能力再申请权限——如果设备不支持,就没必要弹权限弹窗了。
两种方式都有道理,flutter_speech选择了先权限后能力的顺序。
五、引擎创建失败的异常处理与降级方案
5.1
可能的失败原因
createEngine可能因为多种原因失败,抛出异常:
| 失败原因 | 错误表现 | 发生概率 |
|---|---|---|
| 语言不支持 | 异常:languagenotsupported | 高(非中文时) |
| 网络不可用 | 异常:network error | 中(在线模式) |
| AI服务未启动 | 异常:servicenotavailable | 低 |
| 系统资源不足 | 异常:resource exhausted | 极低 |
| 引擎已存在 | 异常:enginealreadyexists | 低 |
5.2
flutter_speech的异常处理
try{this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:1});console.info(TAG,`enginecreated
successfully
`);this.setupListener();this.channel?.invokeMethod('speech.onSpeechAvailability',true);result.success(true);}catch(e){console.error(TAG,`activateerror:
${JSON.stringify(e)}`);result.error('SPEECH_ACTIVATION_ERROR',`Failedactivate
recognition:
${JSON.stringify(e)}`,null);}整个activate方法被try-catch包裹,任何异常都会被捕获并通过result.error返回给Dart层。
5.3
错误信息的序列化
注意异常对象e的序列化方式:
//用JSON.stringify序列化错误对象
console.error(TAG,`activateerror:
${JSON.stringify(e)}`);为什么用JSON.stringify而不是e.message?因为OpenHarmony的异常对象结构可能和标准的Error不同,JSON.stringify可以输出完整的错误信息,包括错误码和详细描述。
//典型的错误对象结构
{"code":1002003,"message":"Languagenot
supported"
}5.4
降级方案
如果在线模式创建失败,可以尝试降级到离线模式(flutter_speech当前未实现,但这是一个好的改进方向):
//降级方案示例
privateasynccreateEngineWithFallback(language:string):Promise<boolean>{//先尝试在线
try{this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:1});console.info(TAG,'onlineengine
created'
);returntrue;}catch(onlineErr){console.warn(TAG,`onlinefailed:
${JSON.stringify(onlineErr)}`);}//降级到离线
try{this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:0});console.info(TAG,'offlineengine
(fallback)'
);returntrue;}catch(offlineErr){console.error(TAG,`offlinealso
failed:
${JSON.stringify(offlineErr)}`);returnfalse;}}5.5
重复创建的处理
如果用户多次调用activate,需要先销毁旧引擎再创建新的:
//当前flutter_speech没有显式处理这种情况
//建议改进:
privateasyncactivate(locale:string,result:MethodResult):Promise<void>{//如果已有引擎,先销毁
if(this.asrEngine){console.info(TAG,'destroyingexisting
one'
);this.destroyEngine();}//创建新引擎...
}🤦实际踩坑:我测试时连续调用了两次activate,第二次创建引擎时偶尔会失败。
后来加了先销毁旧引擎的逻辑就好了。
虽然Core
Speech
Kit理论上应该能处理这种情况,但保险起见还是自己管理好引擎的生命周期。
六、activate
流程图
activate(locale,result)
error('SPEECH_CONTEXT_ERROR')
return
error('SPEECH_PERMISSION_DENIED')
return
error('ERROR_NO_SPEECH_RECOGNITION_AVAILABLE')
return
error('ERROR_LANGUAGE_NOT_SUPPORTED')
return
error('SPEECH_ACTIVATION_ERROR')
return
channel.invokeMethod('speech.onSpeechAvailability',
true)
result.success(true)
6.2
完整源码(带注释)
privateasyncactivate(locale:string,result:MethodResult):Promise<void>{try{console.info(TAG,`activatecalled
locale:
${locale}`);//==========
第1步:权限申请
==========
if(this.abilityContext){console.info(TAG,`requestingmicrophone
permission...
`);constatManager=abilityAccessCtrl.createAtManager();constgrantResult=awaitatManager.requestPermissionsFromUser(this.abilityContext,['ohos.permission.MICROPHONE']);constallGranted=grantResult.authResults.every((status:number)=>status===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED);console.info(TAG,`permissiongranted:
${allGranted}`);if(!allGranted){result.error('SPEECH_PERMISSION_DENIED','Microphonepermission
denied'
,null);return;}}else{console.error(TAG,`abilityContextnull
`);result.error('SPEECH_CONTEXT_ERROR','UIAbilityContextnot
available'
,null);return;}//==========
第2步:能力检测
==========
if(!canIUse('SystemCapability.AI.SpeechRecognizer')){result.error('ERROR_NO_SPEECH_RECOGNITION_AVAILABLE','Devicedoes
recognition'
,null);return;}//==========
第3步:语言校验
==========
constlanguage=this.convertLocale(locale);if(!this.isSupportedLocale(language)){result.error('ERROR_LANGUAGE_NOT_SUPPORTED',`Language"
${locale}"not
HarmonyOS.
`,null);return;}//==========
第4步:创建引擎
==========
console.info(TAG,`creatingengine
language:
${language}`);this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:1});console.info(TAG,`enginecreated
successfully
`);//==========
第5步:设置监听器
==========
this.setupListener();//==========
==========
this.channel?.invokeMethod('speech.onSpeechAvailability',true);result.success(true);}catch(e){console.error(TAG,`activateerror:
${JSON.stringify(e)}`);result.error('SPEECH_ACTIVATION_ERROR',`Failedactivate
recognition:
${JSON.stringify(e)}`,null);}}6.3
各步骤的耗时分析
| 步骤 | 预估耗时 | 是否异步 | 说明 |
|---|---|---|---|
| 权限申请 | 0-5秒 | ✅ await | 取决于用户操作速度 |
| 能力检测 | <1ms | ❌ 同步 | 系统调用,极快 |
| 语言校验 | <1ms | ❌ 同步 | 字符串比较 |
| 引擎创建 | 500ms-3秒 | ✅ await | 取决于网络和系统状态 |
| 设置监听器 | <1ms | ❌ 同步 | 注册回调 |
| 通知Dart | <1ms | ❌ 异步发送 | 不等待结果 |
📌整个activate方法的总耗时:最快约500ms(权限已授予+引擎快速创建),最慢可能超过5秒(首次权限弹窗+网络慢)。
Dart层应该在调用activate时显示loading状态。
七、引擎创建成功后的操作
7.1
setupListener
引擎创建成功后,立即设置监听器:
this.setupListener();这一步在下一篇(第13篇)会详细讲解。
简单来说就是注册onStart、onResult、onComplete、onError四个回调。
7.2
通知Dart层
this.channel?.invokeMethod('speech.onSpeechAvailability',true);这行代码通过MethodChannel向Dart层发送一个事件,告诉Dart层"语音识别引擎已就绪"。
Dart层收到后会调用availabilityHandler(true)回调。
7.3
返回结果
result.success(true);最后通过result.success(true)告诉Dart层activate方法执行成功。
Dart层的Future会以true完成。
💡注意区分两种通知:
result.success(true)是对activate方法调用的直接响应(同步语义),channel.invokeMethod('speech.onSpeechAvailability',true)是一个异步事件通知。
两者都需要,因为Dart层可能分别监听方法返回值和事件回调。
八、与Android引擎创建的对比
8.1
代码对比
Android:
privatevoidactivate(Stringlocale,MethodChannel.Resultresult){//同步创建
speechRecognizer=SpeechRecognizer.createSpeechRecognizer(activity);speechRecognizer.setRecognitionListener(recognitionListener);//检查是否可用
if(SpeechRecognizer.isRecognitionAvailable(activity)){channel.invokeMethod("speech.onSpeechAvailability",true);result.success(true);}else{result.success(false);}}OpenHarmony:
privateasyncactivate(locale:string,result:MethodResult):Promise<void>{//异步创建
this.asrEngine=awaitspeechRecognizer.createEngine({language:language,online:1});this.setupListener();this.channel?.invokeMethod('speech.onSpeechAvailability',true);result.success(true);}8.2
关键差异
| 差异点 | Android | OpenHarmony |
|---|---|---|
| 创建方式 | 同步 | 异步(await) |
| 语言参数 | 不在创建时指定 | 创建时指定 |
| 在线/离线 | 不在创建时指定 | 创建时指定 |
| 能力检测 | isRecognitionAvailable() | canIUse() |
| 权限申请 | 分离在另一个方法 | 集成在activate中 |
🎯OpenHarmony的设计更"前置":语言和模式在创建引擎时就确定了,而Android是在startListening时通过Intent指定。
这意味着OpenHarmony如果要切换语言,需要重新创建引擎。
总结
本文详细讲解了flutter_speech中语音识别引擎的创建过程:
- createEngine参数:language(BCP
47格式)和online(1=在线,0=离线)
- locale转换:
convertLocale将下划线格式转为连字符格式 - 能力检测:
canIUse('SystemCapability.AI.SpeechRecognizer') - 异步创建:必须用await等待引擎创建完成
- 异常处理:try-catch捕获所有异常,通过result.error返回
- 创建后操作:setupListener
+
返回成功
下一篇我们深入语音识别监听器的实现——setupListener方法中四个回调的详细解析。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
/>
相关资源:
- Core
Speech
createEngine文档
- canIUse系统能力检测
- Android
SpeechRecognizer.createSpeechRecognizer
- flutter_speech
OpenHarmony源码
- OpenHarmony
AI能力概述
- 开源鸿蒙跨平台社区
- Flutter-OHOS适配指南


