Flutter三方库适配OpenHarmony【flutter_speech】— Android 端实现分析

前言

欢迎加入开源鸿蒙跨平台社区: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;
  }
}

两个平台的释放逻辑基本一致:

  1. 取消MethodCallHandler注册
  2. 如果正在监听,先取消
  3. 销毁引擎
  4. 置空引用
  5. 重置状态

⚠️ 切记:资源释放的顺序很重要。必须先取消监听,再销毁引擎。如果顺序反了,可能会触发引擎内部的异常。

六、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);
}

错误发生时的处理:

  1. 将isListening设为false
  2. 通知Dart层引擎不可用
  3. 传递错误码给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端的实现,我总结了几个值得借鉴的设计模式:

  1. 状态标志位 :用isListening布尔值管理监听状态,简单有效
  2. 结果缓存 :用lastTranscription缓存最后一次识别结果,避免丢失
  3. 防重入:在开始新的监听前,先取消之前的监听
  4. 优雅降级:权限拒绝时返回明确的错误码,而不是崩溃

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端的实现,我们获得了以下关键认知:

  1. 类结构映射:Android的FlutterPlugin/MethodCallHandler/ActivityAware三件套,在OpenHarmony上有完全对应的接口
  2. API差异:SpeechRecognizer和Core Speech Kit的API设计不同,但核心流程一致
  3. 回调简化:Android有10个回调,OpenHarmony只有5个,映射关系清晰
  4. 权限模型:两个平台的权限申请逻辑类似,OpenHarmony的async/await写法更简洁
  5. 资源管理:释放策略完全一致,都是"取消监听→销毁引擎→置空引用"

下一篇我们来看iOS/macOS端的实现,对比三个平台的差异,为OpenHarmony适配建立更全面的参考。

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


相关资源:

相关推荐
纪念 2295 分钟前
C++ string(三)
android·c++
纪念 22917 分钟前
c++ string(最终篇)
android·c++
aqi009 小时前
鸿蒙版本的电子书阅读APP开放源码啦
android·华为·ai编程·harmonyos·鸿蒙
墨天梦10 小时前
B19_SunnyWeather到阅读客户端
android·kotlin
美狐美颜sdk11 小时前
直播APP源码可以直接接入视频美颜sdk吗?技术方案详解
android·人工智能·音视频·美颜sdk·直播美颜sdk
yuanlaile13 小时前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
用户693717500138413 小时前
2026,程序员的时代拐点到了
android·前端·后端
xq952714 小时前
HDC Assistant 插件横空出世
harmonyos
浪潮IT馆15 小时前
Android Studio 非最新版本下载安装教程
android·ide·android studio
xiaozongt198916 小时前
MasterGo + Claude Code 生成 Android 布局:完整实践指南
android