Flutter三方库适配OpenHarmony【flutter_speech】— 错误处理与异常恢复
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net前20篇我们把flutter_speech的核心功能都讲完了,从第21篇开始进入进阶实践阶段。今天先讲错误处理——这是决定插件"能不能用"和"好不好用"之间的关键差距。一个没有错误处理的插件,在理想环境下跑得很好,一到真实场景就各种崩溃。网络断了怎么办?权限被拒了怎么办?引擎创建失败怎么办
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
前20篇我们把flutter_speech的核心功能都讲完了,从第21篇开始进入进阶实践阶段。今天先讲错误处理——这是决定插件"能不能用"和"好不好用"之间的关键差距。
一个没有错误处理的插件,在理想环境下跑得很好,一到真实场景就各种崩溃。网络断了怎么办?权限被拒了怎么办?引擎创建失败怎么办?这些问题都需要优雅地处理。
💡 本文重点:梳理flutter_speech中所有的错误处理路径,分析每种错误的成因和恢复策略。
一、常见错误码分类与含义
1.1 flutter_speech自定义错误码
| 错误码 | 触发位置 | 含义 | 严重程度 |
|---|---|---|---|
| SPEECH_PERMISSION_DENIED | activate | 麦克风权限被拒绝 | ⭐⭐⭐⭐⭐ |
| SPEECH_CONTEXT_ERROR | activate | UIAbilityContext不可用 | ⭐⭐⭐⭐⭐ |
| ERROR_NO_SPEECH_RECOGNITION_AVAILABLE | activate | 设备不支持语音识别 | ⭐⭐⭐⭐⭐ |
| ERROR_LANGUAGE_NOT_SUPPORTED | activate | 语言不支持 | ⭐⭐⭐⭐ |
| SPEECH_ACTIVATION_ERROR | activate | 引擎创建失败(通用) | ⭐⭐⭐⭐ |
| ERROR_ENGINE_NOT_INITIALIZED | startListening | 引擎未初始化 | ⭐⭐⭐ |
| ERROR_SPEECH_LISTEN | startListening | 启动监听失败 | ⭐⭐⭐ |
1.2 Core Speech Kit引擎错误码
这些错误码通过onError回调传递:
| 错误码 | 含义 | 常见原因 |
|---|---|---|
| 0 | 无错误 | - |
| 1 | 网络超时 | 网络不稳定 |
| 2 | 网络异常 | 无网络连接 |
| 3 | 音频异常 | 麦克风被占用 |
| 4 | 引擎忙 | 上一次识别未结束 |
| 5 | 无语音输入 | VAD超时,用户没说话 |
| 6 | 识别失败 | 服务端错误 |
1.3 错误分类
按可恢复性分类:
| 类别 | 错误 | 恢复方式 |
|---|---|---|
| 不可恢复 | 设备不支持、权限永久拒绝 | 提示用户,禁用功能 |
| 需用户操作 | 权限首次拒绝、无网络 | 引导用户操作后重试 |
| 自动恢复 | 网络超时、引擎忙、无语音输入 | 自动重试 |
二、引擎未初始化错误处理
2.1 错误场景
用户在没有调用activate的情况下直接调用listen:
private startListening(result: MethodResult): void {
if (!this.asrEngine) {
result.error('ERROR_ENGINE_NOT_INITIALIZED',
'Speech engine not initialized. Call activate first.', null);
return;
}
// ...
}
2.2 Dart层的处理
_speech.listen().then((result) {
setState(() => _isListening = result);
}).catchError((e) {
if (e is PlatformException && e.code == 'ERROR_ENGINE_NOT_INITIALIZED') {
// 自动尝试激活
_speech.activate('zh_CN').then((_) => _speech.listen());
}
});
2.3 防御性设计
flutter_speech的示例App通过UI状态来防止这种错误——_speechRecognitionAvailable为false时,Listen按钮不可点击。这是前端防御,比后端报错更好的用户体验。
三、权限拒绝场景的优雅降级
3.1 权限拒绝的两种情况
| 情况 | 表现 | 处理方式 |
|---|---|---|
| 首次拒绝 | 用户点击"拒绝" | 提示并引导重新授权 |
| 永久拒绝 | 用户勾选"不再询问"后拒绝 | 引导用户去系统设置 |
3.2 原生端的处理
const grantResult = await atManager.requestPermissionsFromUser(
this.abilityContext, ['ohos.permission.MICROPHONE']
);
const allGranted = grantResult.authResults.every(
(status: number) => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
);
if (!allGranted) {
result.error('SPEECH_PERMISSION_DENIED', 'Microphone permission denied', null);
return;
}
3.3 Dart层的降级策略
_speech.activate('zh_CN').then((res) {
setState(() => _speechRecognitionAvailable = res);
}).catchError((e) {
if (e is PlatformException) {
switch (e.code) {
case 'SPEECH_PERMISSION_DENIED':
_showPermissionDeniedDialog();
break;
case 'ERROR_NO_SPEECH_RECOGNITION_AVAILABLE':
_showDeviceNotSupportedMessage();
break;
default:
_showGenericErrorMessage(e.message ?? 'Unknown error');
}
}
setState(() => _speechRecognitionAvailable = false);
});
3.4 引导用户去设置页
void _showPermissionDeniedDialog() {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('需要麦克风权限'),
content: Text('语音识别需要麦克风权限才能工作。请在系统设置中开启。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(ctx);
// OpenHarmony打开应用设置页
// 需要通过MethodChannel调用原生端的设置跳转
},
child: Text('去设置'),
),
],
),
);
}
四、网络异常对在线识别的影响
4.1 网络异常的表现
flutter_speech使用在线识别模式(online: 1),网络异常会导致:
| 阶段 | 网络异常的影响 |
|---|---|
| createEngine | 引擎创建失败,抛异常 |
| startListening | 可能成功(音频采集不需要网络) |
| 识别过程中 | onError回调,错误码1或2 |
4.2 引擎创建时的网络异常
try {
this.asrEngine = await speechRecognizer.createEngine({
language: language,
online: 1
});
} catch (e) {
// 网络异常时,createEngine可能抛出异常
console.error(TAG, `activate error: ${JSON.stringify(e)}`);
result.error('SPEECH_ACTIVATION_ERROR',
`Failed to activate speech recognition: ${JSON.stringify(e)}`, null);
}
4.3 识别过程中的网络异常
onError(sessionId: string, errorCode: number, errorMessage: string): void {
console.error(TAG, `onError: code=${errorCode}, message=${errorMessage}`);
plugin.isListening = false;
channel?.invokeMethod('speech.onSpeechAvailability', false);
channel?.invokeMethod('speech.onError', errorCode);
}
错误码1(网络超时)和2(网络异常)都会触发onError。
4.4 网络恢复后的重试
void errorHandler() => activateSpeechRecognizer();
flutter_speech示例App的策略是自动重新初始化。网络恢复后,重新activate通常能成功。
🤔 改进建议:可以在重试前先检查网络状态,避免在无网络时反复重试:
void errorHandler() async {
// 等待一段时间再重试
await Future.delayed(Duration(seconds: 2));
activateSpeechRecognizer();
}
五、errorHandler 重新激活策略分析
5.1 当前实现
void errorHandler() => activateSpeechRecognizer();
示例App的错误处理策略极其简单——无条件重新初始化。
5.2 执行流程
onError触发
→ Dart: errorHandler()
→ activateSpeechRecognizer()
→ _speech = SpeechRecognition()
→ 设置所有回调
→ activate('zh_CN')
→ 成功 → _speechRecognitionAvailable = true
→ 失败 → _speechRecognitionAvailable = false
(不会再次触发errorHandler,因为activate失败不走onError)
5.3 潜在问题:无限重试循环
如果错误在识别过程中持续发生(比如麦克风硬件故障),会出现:
listen → onError → errorHandler → activate → listen → onError → errorHandler → ...
不过实际上这个循环不会真的无限——因为errorHandler只调用activateSpeechRecognizer,不会自动调用listen。用户需要手动点击Listen按钮才会再次开始识别。
5.4 改进方案:带退避的重试
int _retryCount = 0;
static const int _maxRetries = 3;
static const List<int> _retryDelays = [1000, 3000, 10000]; // 毫秒
void errorHandler() async {
if (_retryCount >= _maxRetries) {
setState(() => _speechRecognitionAvailable = false);
_showErrorSnackBar('语音识别暂时不可用,请稍后再试');
_retryCount = 0;
return;
}
final delay = _retryDelays[_retryCount];
_retryCount++;
await Future.delayed(Duration(milliseconds: delay));
activateSpeechRecognizer();
}
// 在activate成功时重置计数
void onSpeechAvailability(bool result) {
if (result) _retryCount = 0;
setState(() => _speechRecognitionAvailable = result);
}
这个方案的优点:
- 有限重试:最多重试3次
- 退避延迟:每次重试间隔递增(1秒、3秒、10秒)
- 用户反馈:超过最大重试次数后提示用户
- 自动重置:成功后重置计数器
5.5 各错误的推荐恢复策略
| 错误 | 推荐策略 | 重试次数 | 用户提示 |
|---|---|---|---|
| 权限拒绝 | 不重试,引导设置 | 0 | 弹窗引导 |
| 设备不支持 | 不重试 | 0 | 永久提示 |
| 语言不支持 | 不重试,切换语言 | 0 | SnackBar |
| 网络超时 | 自动重试 | 3次 | 检查网络 |
| 网络异常 | 等待网络恢复 | 3次 | 检查网络 |
| 音频异常 | 自动重试 | 1次 | 检查麦克风 |
| 引擎忙 | 延迟重试 | 2次 | 稍后再试 |
| 无语音输入 | 不重试(正常行为) | 0 | 提示说话 |
六、try-catch 在插件中的使用模式
6.1 flutter_speech中的try-catch分布
| 方法 | 有try-catch | catch中的行为 |
|---|---|---|
| activate | ✅ | result.error + 日志 |
| startListening | ✅ | result.error + 重置状态 + 日志 |
| cancel | ✅ | result.success(true) + 日志 |
| stop | ✅ | result.success(true) + 日志 |
| destroyEngine | ✅ | 仅日志 |
6.2 两种catch策略
策略A:报告错误(activate、startListening)
catch (e) {
result.error('ERROR_CODE', `message: ${JSON.stringify(e)}`, null);
}
策略B:静默成功(cancel、stop)
catch (e) {
console.error(TAG, `error: ${JSON.stringify(e)}`);
result.success(true); // 即使出错也返回成功
}
选择哪种策略取决于操作的语义:
- activate和listen是"请求做某事",失败了应该告诉调用方
- cancel和stop是"请求停止",即使底层出错,从用户角度来说"停止"已经完成了
6.3 JSON.stringify序列化错误
console.error(TAG, `error: ${JSON.stringify(e)}`);
为什么用JSON.stringify而不是e.message?因为OpenHarmony的异常对象可能不是标准的Error类型,JSON.stringify能输出完整的错误信息:
{"code": 1002003, "message": "Language not supported"}
七、错误处理的完整流程图
Dart调用activate("zh_CN")
│
├── abilityContext == null?
│ └── 是 → SPEECH_CONTEXT_ERROR → Dart catchError
│
├── 权限申请
│ └── 拒绝 → SPEECH_PERMISSION_DENIED → Dart catchError
│
├── canIUse检测
│ └── 不支持 → ERROR_NO_SPEECH_RECOGNITION_AVAILABLE → Dart catchError
│
├── 语言校验
│ └── 不支持 → ERROR_LANGUAGE_NOT_SUPPORTED → Dart catchError
│
├── createEngine
│ └── 异常 → SPEECH_ACTIVATION_ERROR → Dart catchError
│
└── 成功 → result.success(true) → Dart then
Dart调用listen()
│
├── asrEngine == null?
│ └── 是 → ERROR_ENGINE_NOT_INITIALIZED → Dart catchError
│
├── startListening
│ └── 异常 → ERROR_SPEECH_LISTEN → Dart catchError
│
└── 成功 → result.success(true) → Dart then
识别过程中
│
├── 正常 → onResult → speech.onSpeech → Dart回调
│
└── 异常 → onError → speech.onError → Dart errorHandler
→ speech.onSpeechAvailability(false)
八、最佳实践总结
8.1 原生端
- 每个公开方法都要try-catch:防止未捕获异常导致崩溃
- 错误码要有意义:让Dart层能根据错误码做不同处理
- 错误信息要包含上下文:包含locale、sessionId等信息便于调试
- 销毁方法绝不抛异常:catch后仅记录日志
8.2 Dart端
- 区分错误类型:根据错误码做不同的恢复策略
- 有限重试:设置最大重试次数,避免无限循环
- 用户反馈:每种错误都应该有对应的UI提示
- 前端防御:通过UI状态防止无效操作,比后端报错更好
8.3 检查清单
- 所有MethodCall分支都有result响应
- 所有异步操作都有try-catch
- 错误码命名清晰、有文档
- Dart层对每种错误码有对应处理
- 重试策略有上限
- 用户能看到有意义的错误提示
总结
本文梳理了flutter_speech中所有的错误处理路径:
- 7个自定义错误码:覆盖权限、能力、语言、引擎、监听等场景
- 6个引擎错误码:网络、音频、引擎状态等运行时错误
- 两种catch策略:关键操作报告错误,停止操作静默成功
- errorHandler自动恢复:简单但有效,建议加重试限制
- 权限降级:区分首次拒绝和永久拒绝,引导用户操作
下一篇我们讲调试技巧与日志分析——如何高效地定位和解决flutter_speech中的问题。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
- Core Speech Kit错误码文档
- Flutter PlatformException文档
- OpenHarmony权限管理
- flutter_speech OpenHarmony源码
- 开源鸿蒙跨平台社区

更多推荐



所有评论(0)