前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
在做OpenHarmony适配之前,我花了不少时间研究Android端的实现。为什么?因为Android端是flutter_speech最成熟的平台实现,代码经过了大量用户的验证,很多设计决策都值得借鉴。
说实话,Android的SpeechRecognizer API我之前只是用过,从来没有深入研究过。这次为了做适配,把Android端的源码翻了个底朝天,收获还挺大的。特别是RecognitionListener的回调设计,直接影响了我后来在OpenHarmony端的实现思路。
今天这篇文章,我会从插件类结构、SpeechRecognizer API、回调处理、权限管理、生命周期五个方面来分析Android端的实现。每个部分都会和OpenHarmony做对比,方便大家建立映射关系。

💡 说明:本文分析的是flutter_speech插件的Android端Java实现。如果你对Android原生开发不太熟悉,可以先跳过本篇,直接看第7篇的OpenHarmony实现。
一、FlutterSpeechRecognitionPlugin 类结构解析
1.1 类声明与接口实现
Android端的插件类实现了多个接口:
java
public class FlutterSpeechRecognitionPlugin implements
FlutterPlugin,
MethodCallHandler,
ActivityAware {
private MethodChannel channel;
private SpeechRecognizer speechRecognizer;
private Activity activity;
private boolean isListening = false;
private String lastTranscription = "";
}
| 接口 | 职责 | OpenHarmony对应 |
|---|---|---|
| FlutterPlugin | 插件生命周期管理 | FlutterPlugin |
| MethodCallHandler | 处理Dart方法调用 | MethodCallHandler |
| ActivityAware | 获取Activity上下文 | AbilityAware |
这三个接口的组合是Flutter插件的标准模式。OpenHarmony端的实现也是这三个接口,只是名称略有不同(ActivityAware → AbilityAware)。
1.2 成员变量分析
java
private MethodChannel channel; // 与Dart通信的通道
private SpeechRecognizer speechRecognizer; // Android语音识别引擎
private Activity activity; // Activity上下文(权限申请需要)
private boolean isListening = false; // 是否正在监听
private String lastTranscription = ""; // 最后一次识别结果
对比OpenHarmony端的成员变量:
| Android | OpenHarmony | 类型差异 |
|---|---|---|
| MethodChannel channel | MethodChannel channel | 相同 |
| SpeechRecognizer | speechRecognizer.SpeechRecognitionEngine | 不同的引擎类型 |
| Activity activity | common.UIAbilityContext abilityContext | 不同的上下文类型 |
| boolean isListening | boolean isListening | 相同 |
| String lastTranscription | string lastTranscription | 相同 |
🎯 关键发现 :两个平台的成员变量几乎一一对应,只是类型不同。这说明适配的核心工作就是类型映射------把Android的类型替换成OpenHarmony的对应类型。
1.3 与OpenHarmony实现的结构对比
把两个平台的类声明放在一起看:
java
// Android
public class FlutterSpeechRecognitionPlugin implements
FlutterPlugin, MethodCallHandler, ActivityAware {
// ...
}
typescript
// OpenHarmony
export default class FlutterSpeechPlugin implements
FlutterPlugin, MethodCallHandler, AbilityAware {
// ...
}
结构几乎一模一样。Flutter-OHOS团队在设计接口时显然参考了Android端的设计,这大大降低了适配的学习成本。
二、Android SpeechRecognizer API 使用详解
2.1 SpeechRecognizer 概述
Android的SpeechRecognizer是系统级的语音识别API,位于android.speech包中:
java
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
| API | 功能 | 说明 |
|---|---|---|
| SpeechRecognizer.createSpeechRecognizer() | 创建引擎 | 需要Context参数 |
| setRecognitionListener() | 设置回调 | RecognitionListener接口 |
| startListening() | 开始识别 | 传入Intent参数 |
| stopListening() | 停止识别 | 获取最终结果 |
| cancel() | 取消识别 | 不返回结果 |
| destroy() | 销毁引擎 | 释放资源 |
2.2 引擎创建流程
java
private void createSpeechRecognizer(String locale) {
if (speechRecognizer != null) {
speechRecognizer.destroy();
}
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(activity);
speechRecognizer.setRecognitionListener(new SpeechRecognitionListener());
// 检查语音识别是否可用
boolean available = SpeechRecognizer.isRecognitionAvailable(activity);
channel.invokeMethod("speech.onSpeechAvailability", available);
}
对比OpenHarmony端:
typescript
// OpenHarmony的引擎创建
this.asrEngine = await speechRecognizer.createEngine({
language: language,
online: 1
});
this.setupListener();
this.channel?.invokeMethod('speech.onSpeechAvailability', true);
| 对比维度 | Android | OpenHarmony |
|---|---|---|
| 创建方式 | 同步创建 | 异步创建(await) |
| 参数 | Context | language + online |
| 语言设置 | 在startListening的Intent中 | 在createEngine时指定 |
| 可用性检查 | isRecognitionAvailable() | canIUse() |
🤔 一个重要差异 :Android的语言设置是在
startListening时通过Intent传入的,而OpenHarmony是在createEngine时就要指定。这意味着在OpenHarmony上,切换语言需要重新创建引擎。
2.3 启动识别
java
private void startListening(String locale) {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, locale);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
speechRecognizer.startListening(intent);
isListening = true;
}
Android通过Intent传递识别参数,这是Android特有的设计模式。OpenHarmony用的是StartParams结构体:
typescript
// OpenHarmony的启动参数
const recognizerParams: speechRecognizer.StartParams = {
sessionId: this.sessionId,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
},
extraParams: {
"recognitionMode": 0,
"vadBegin": 2000,
"vadEnd": 3000,
"maxAudioDuration": 60000
}
};
this.asrEngine.startListening(recognizerParams);
参数对比:
| 参数 | Android | OpenHarmony | 说明 |
|---|---|---|---|
| 语言 | EXTRA_LANGUAGE | createEngine时指定 | 设置时机不同 |
| 识别模式 | LANGUAGE_MODEL_FREE_FORM | recognitionMode: 0 | 自由文本模式 |
| 部分结果 | EXTRA_PARTIAL_RESULTS: true | 默认支持 | OHOS默认开启 |
| 音频格式 | 系统自动处理 | audioInfo手动指定 | OHOS需要显式配置 |
| VAD | 系统默认 | vadBegin/vadEnd | OHOS可精细控制 |
三、RecognitionListener 回调处理流程
3.1 RecognitionListener 接口
Android的RecognitionListener接口定义了十个回调方法:
java
class SpeechRecognitionListener implements RecognitionListener {
@Override
public void onReadyForSpeech(Bundle params) {
// 引擎准备就绪,可以开始说话
channel.invokeMethod("speech.onRecognitionStarted", null);
}
@Override
public void onBeginningOfSpeech() {
// 检测到用户开始说话
}
@Override
public void onRmsChanged(float rmsdB) {
// 音量变化(可用于显示音量动画)
}
@Override
public void onBufferReceived(byte[] buffer) {
// 接收到音频数据
}
@Override
public void onEndOfSpeech() {
// 用户停止说话
}
@Override
public void onError(int error) {
// 发生错误
isListening = false;
channel.invokeMethod("speech.onError", error);
}
@Override
public void onResults(Bundle results) {
// 最终识别结果
ArrayList<String> matches = results.getStringArrayList(
SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && !matches.isEmpty()) {
lastTranscription = matches.get(0);
channel.invokeMethod("speech.onRecognitionComplete", lastTranscription);
}
isListening = false;
}
@Override
public void onPartialResults(Bundle partialResults) {
// 部分识别结果(实时)
ArrayList<String> matches = partialResults.getStringArrayList(
SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && !matches.isEmpty()) {
lastTranscription = matches.get(0);
channel.invokeMethod("speech.onSpeech", lastTranscription);
}
}
@Override
public void onEvent(int eventType, Bundle params) {
// 其他事件
}
}
3.2 回调映射到Dart层
Android的十个回调最终映射到Dart层的五个事件:
| Android回调 | Dart事件 | 映射关系 |
|---|---|---|
| onReadyForSpeech | speech.onRecognitionStarted | 直接映射 |
| onPartialResults | speech.onSpeech | 取第一个结果 |
| onResults | speech.onRecognitionComplete | 取第一个结果 |
| onError | speech.onError | 传递错误码 |
| onBeginningOfSpeech | (未映射) | 被忽略 |
| onRmsChanged | (未映射) | 被忽略 |
| onBufferReceived | (未映射) | 被忽略 |
| onEndOfSpeech | (未映射) | 被忽略 |
| onEvent | (未映射) | 被忽略 |
📌 设计取舍:Android有十个回调,但flutter_speech只用了四个。被忽略的回调(如onRmsChanged音量变化)在某些场景下其实很有用,但为了保持API简洁,原作者选择了不暴露。
3.3 对比OpenHarmony的回调
OpenHarmony的setListener只有五个回调,和Dart层的五个事件刚好对应:
typescript
this.asrEngine.setListener({
onStart(sessionId, eventMessage) {
// → speech.onRecognitionStarted
channel?.invokeMethod('speech.onRecognitionStarted', null);
},
onEvent(sessionId, eventCode, eventMessage) {
// 事件通知(类似Android的onEvent)
},
onResult(sessionId, result) {
// → speech.onSpeech + speech.onRecognitionComplete
channel?.invokeMethod('speech.onSpeech', result.result);
if (result.isLast) {
channel?.invokeMethod('speech.onRecognitionComplete', result.result);
}
},
onComplete(sessionId, eventMessage) {
// 识别完成
},
onError(sessionId, errorCode, errorMessage) {
// → speech.onError
channel?.invokeMethod('speech.onError', errorCode);
}
});
| 对比维度 | Android | OpenHarmony |
|---|---|---|
| 回调数量 | 10个 | 5个 |
| 部分结果 | onPartialResults | onResult (isLast=false) |
| 最终结果 | onResults | onResult (isLast=true) |
| 结果格式 | Bundle (ArrayList) | SpeechRecognitionResult |
| 错误信息 | int errorCode | int errorCode + String message |
💡 OpenHarmony的设计更简洁 :用一个
onResult回调通过isLast字段区分部分结果和最终结果,比Android的两个独立回调(onPartialResults + onResults)更优雅。
四、权限申请机制:RECORD_AUDIO 动态权限
4.1 Android权限模型
Android从6.0开始引入了动态权限 机制,语音识别需要RECORD_AUDIO权限:
xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
java
// 动态权限申请
private void requestPermission() {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_CODE_SPEECH);
}
}
4.2 OpenHarmony权限模型对比
OpenHarmony的权限机制和Android类似但有差异:
json5
// module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:microphone_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
typescript
// 动态权限申请
const atManager = abilityAccessCtrl.createAtManager();
const grantResult = await atManager.requestPermissionsFromUser(
this.abilityContext,
['ohos.permission.MICROPHONE']
);
const allGranted = grantResult.authResults.every(
(status: number) => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
);
| 对比维度 | Android | OpenHarmony |
|---|---|---|
| 权限名称 | RECORD_AUDIO | ohos.permission.MICROPHONE |
| 声明位置 | AndroidManifest.xml | module.json5 |
| 申请API | ActivityCompat.requestPermissions | atManager.requestPermissionsFromUser |
| 上下文 | Activity | UIAbilityContext |
| 回调方式 | onRequestPermissionsResult | Promise (async/await) |
🤔 个人感受 :OpenHarmony的权限申请用async/await比Android的回调方式写起来舒服多了。Android那个
onRequestPermissionsResult回调嵌套深了很头疼。
4.3 权限拒绝处理
java
// Android - 权限被拒绝
if (!hasPermission) {
result.error("SPEECH_PERMISSION_DENIED",
"Microphone permission denied", null);
return;
}
typescript
// OpenHarmony - 权限被拒绝
if (!allGranted) {
result.error('SPEECH_PERMISSION_DENIED',
'Microphone permission denied', null);
return;
}
两个平台的错误处理逻辑完全一致,都是返回SPEECH_PERMISSION_DENIED错误码。这种一致性对Dart层的错误处理很友好。
五、Activity 生命周期管理与资源释放
5.1 ActivityAware 生命周期
java
// Android的Activity生命周期回调
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
this.activity = binding.getActivity();
}
@Override
public void onDetachedFromActivity() {
this.activity = null;
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
this.activity = binding.getActivity();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
this.activity = null;
}
Android的ActivityAware有四个 生命周期方法,而OpenHarmony的AbilityAware只有两个:
typescript
// OpenHarmony的Ability生命周期
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.abilityContext = binding.getAbility().context;
}
onDetachedFromAbility(): void {
this.abilityContext = null;
}
| Android | OpenHarmony | 说明 |
|---|---|---|
| onAttachedToActivity | onAttachedToAbility | 获取上下文 |
| onDetachedFromActivity | onDetachedFromAbility | 释放上下文 |
| onReattachedToActivityForConfigChanges | (无) | 配置变更重连 |
| onDetachedFromActivityForConfigChanges | (无) | 配置变更断开 |
📌 为什么OpenHarmony少了两个方法:Android的"ConfigChanges"回调是为了处理屏幕旋转等配置变更场景。OpenHarmony的Ability模型处理配置变更的方式不同,不需要这两个额外的回调。
5.2 资源释放策略
java
// Android的资源释放
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
if (speechRecognizer != null) {
speechRecognizer.destroy();
speechRecognizer = null;
}
isListening = false;
}
typescript
// OpenHarmony的资源释放
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.channel != null) {
this.channel.setMethodCallHandler(null);
}
this.destroyEngine();
}
private destroyEngine(): void {
if (this.asrEngine) {
if (this.isListening) {
this.asrEngine.cancel(this.sessionId);
}
this.asrEngine.shutdown();
this.asrEngine = null;
this.isListening = false;
}
}
两个平台的释放逻辑基本一致:
- 取消MethodCallHandler注册
- 如果正在监听,先取消
- 销毁引擎
- 置空引用
- 重置状态
⚠️ 切记:资源释放的顺序很重要。必须先取消监听,再销毁引擎。如果顺序反了,可能会触发引擎内部的异常。
六、Android端的错误处理
6.1 SpeechRecognizer 错误码
Android定义了一系列标准错误码:
| 错误码 | 常量名 | 含义 | 常见原因 |
|---|---|---|---|
| 1 | ERROR_NETWORK_TIMEOUT | 网络超时 | 网络不稳定 |
| 2 | ERROR_NETWORK | 网络错误 | 无网络连接 |
| 3 | ERROR_AUDIO | 音频错误 | 麦克风被占用 |
| 4 | ERROR_SERVER | 服务器错误 | 识别服务异常 |
| 5 | ERROR_CLIENT | 客户端错误 | 参数错误 |
| 6 | ERROR_SPEECH_TIMEOUT | 语音超时 | 用户没说话 |
| 7 | ERROR_NO_MATCH | 无匹配结果 | 无法识别 |
| 8 | ERROR_RECOGNIZER_BUSY | 识别器忙 | 重复调用 |
| 9 | ERROR_INSUFFICIENT_PERMISSIONS | 权限不足 | 未授权 |
6.2 错误处理流程
java
@Override
public void onError(int error) {
isListening = false;
channel.invokeMethod("speech.onSpeechAvailability", false);
channel.invokeMethod("speech.onError", error);
}
错误发生时的处理:
- 将
isListening设为false - 通知Dart层引擎不可用
- 传递错误码给Dart层
6.3 OpenHarmony的错误处理对比
typescript
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);
}
处理逻辑完全一致。OpenHarmony额外提供了errorMessage字符串,但当前Dart层的errorHandler是VoidCallback类型,无法接收这个信息。
七、从Android实现中学到的经验
7.1 设计模式总结
通过分析Android端的实现,我总结了几个值得借鉴的设计模式:
- 状态标志位 :用
isListening布尔值管理监听状态,简单有效 - 结果缓存 :用
lastTranscription缓存最后一次识别结果,避免丢失 - 防重入:在开始新的监听前,先取消之前的监听
- 优雅降级:权限拒绝时返回明确的错误码,而不是崩溃
7.2 适配OpenHarmony时的注意事项
| 注意事项 | Android做法 | OpenHarmony需要调整 |
|---|---|---|
| 引擎创建 | 同步创建 | 改为异步(await) |
| 语言设置 | startListening时传入 | createEngine时指定 |
| 音频参数 | 系统自动处理 | 需要手动配置AudioInfo |
| VAD控制 | 系统默认 | 可通过extraParams配置 |
| 会话管理 | 无需sessionId | 需要传入sessionId |
7.3 代码复用策略
虽然不能直接复用Android的Java代码,但逻辑结构可以完全复用:
Android逻辑 OpenHarmony逻辑
────────── ────────────────
创建SpeechRecognizer → 创建SpeechRecognitionEngine
设置RecognitionListener → 调用setListener
构建Intent参数 → 构建StartParams
startListening → startListening
stopListening → finish
cancel → cancel
destroy → shutdown + 置null
八、完整的方法调用时序图
8.1 正常流程时序
Dart层 Android原生层 系统服务
│ │ │
│ activate │ │
│───────────────>│ createSpeechRecognizer │
│ │───────────────────────>│
│ │<───────────────────────│
│ │ onSpeechAvailability │
│<───────────────│ (true) │
│ │ │
│ listen │ │
│───────────────>│ startListening(Intent) │
│ │───────────────────────>│
│ │ onReadyForSpeech │
│ │<───────────────────────│
│ onRecognition │ │
│ Started │ │
│<───────────────│ │
│ │ onPartialResults │
│ │<───────────────────────│
│ onSpeech │ │
│<───────────────│ │
│ │ onResults │
│ │<───────────────────────│
│ onRecognition │ │
│ Complete │ │
│<───────────────│ │
8.2 错误流程时序
Dart层 Android原生层 系统服务
│ │ │
│ listen │ │
│───────────────>│ startListening │
│ │───────────────────────>│
│ │ onError(ERROR_NETWORK) │
│ │<───────────────────────│
│ onSpeech │ │
│ Availability │ │
│ (false) │ │
│<───────────────│ │
│ onError │ │
│<───────────────│ │
│ │ │
│ [errorHandler重新activate] │
总结
通过深入分析Android端的实现,我们获得了以下关键认知:
- 类结构映射:Android的FlutterPlugin/MethodCallHandler/ActivityAware三件套,在OpenHarmony上有完全对应的接口
- API差异:SpeechRecognizer和Core Speech Kit的API设计不同,但核心流程一致
- 回调简化:Android有10个回调,OpenHarmony只有5个,映射关系清晰
- 权限模型:两个平台的权限申请逻辑类似,OpenHarmony的async/await写法更简洁
- 资源管理:释放策略完全一致,都是"取消监听→销毁引擎→置空引用"
下一篇我们来看iOS/macOS端的实现,对比三个平台的差异,为OpenHarmony适配建立更全面的参考。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: