Flutter/OpenHarmony 三方库适配实战:pda_scanner 兼容层
Flutter/OpenHarmony 三方库适配实战:pda_scanner 兼容层
PDA 扫码插件和普通相机扫码插件有一个关键区别:它通常不负责打开摄像头,
而是依赖 Android 设备厂商提供的扫码广播、EMDK 或系统服务。pda_scanner
原库正是这种架构,支持斑马、海康威视、远望谷和思必拓等设备。
本次适配版本为 1.0.4-ohos.1,目标分为两层:
- 保留原有 Dart API、Android 厂商实现和音频提示能力。
- 新增 OHOS HAR 兼容层,让初始化、设备信息、回调管理、桌面跳转和通道
生命周期先稳定下来,为后续接入具体 OHOS PDA SDK 留下明确入口。

一、项目信息与适配边界
| 项目 | 内容 |
|---|---|
| 仓库名称 | flutter_plugin_pda_scanner |
| 原始包名 | pda_scanner |
| 适配版本 | 1.0.4-ohos.1 |
| 方法通道 | org.jerome/pda_scanner |
| 原生入口 | PdaScannerPlugin |
| OHOS 扫码能力 | 当前返回未检测到统一厂商桥接 |
整体链路如下:
Android vendor scanner
|
v
org.jerome/pda_scanner
|
v
PdaUtils.on(tag, callback)
OHOS compatibility layer
|
+-- initScanner
+-- getPDAModel
+-- getPlatformVersion
+-- navigateToSystemHome
|
v
future vendor OHOS scanner SDK
|
v
sendBarcodeToFlutter
这次最重要的判断是:isThisPDASupported() 在没有 OHOS 厂商扫码桥接时必须
返回 false。如果为了让页面显示“支持”而返回 true,调用方会继续等待一个
永远不会到来的条码事件,排查成本反而更高。
二、先保留 Dart API,不让业务代码分叉
原库的 Dart 类 PdaUtils 已经把扫码订阅、设备信息、提示音和日志封装成静态
接口。业务代码通常在 main() 中先初始化:
void main() async {
await PdaUtils.init();
runApp(const MyApp());
}
再用 tag 管理多个页面的条码回调:
PdaUtils.on('inventory', (String barcode) {
debugPrint('inventory: $barcode');
});
PdaUtils.off('inventory');
PdaUtils.offAll();
适配时没有新增 PdaUtilsOhos 之类的类,也没有要求业务页面增加 OHOS 分支。
Dart 层只修改平台判断和提示音加载时机,方法名、参数结构和回调签名保持不变。
平台判断也从 Platform.isAndroid 改成 Flutter 框架统一提供的
defaultTargetPlatform:
if (defaultTargetPlatform != TargetPlatform.android &&
defaultTargetPlatform != TargetPlatform.ohos) {
throw Exception(['PDA插件只支持 Android 或 OpenHarmony 系统设备!']);
}
这样在 OHOS Flutter 工具链中可以识别 TargetPlatform.ohos,在主机单元测试中
也不会访问只在 OHOS 运行时存在的 Platform.isOhos。
三、让 Flutter OHOS 识别原生入口
根 pubspec.yaml 保留 Android 的包名和插件类,只增加 OHOS 平台声明:
version: 1.0.4-ohos.1
flutter:
plugin:
platforms:
android:
package: org.jerome.pda_scanner
pluginClass: PdaScannerPlugin
ohos:
pluginClass: PdaScannerPlugin
OHOS 模块配置为普通 HAR:
{
"module": {
"name": "pda_scanner",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
入口文件只导出插件类:
import PdaScannerPlugin
from './src/main/ets/components/plugin/PdaScannerPlugin';
export default PdaScannerPlugin;
Flutter 工具链需要同时看到 pubspec.yaml、index.ets 和 Hvigor 配置。少任何
一层,都可能出现 Dart 通道已经创建,但 OHOS 原生类没有被注册的情况。
四、ArkTS 插件的通道实现
OHOS 插件实现 FlutterPlugin、MethodCallHandler 和 AbilityAware。Engine
绑定时创建通道,Ability 绑定时保存当前 UIAbility Context,分离时清空引用:
const CHANNEL = 'org.jerome/pda_scanner';
export default class PdaScannerPlugin
implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private uiAbilityContext: common.UIAbilityContext | null = null;
private initialized = false;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL);
this.channel.setMethodCallHandler(this);
}
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.uiAbilityContext = binding.getAbility().context;
}
onDetachedFromAbility(): void {
this.uiAbilityContext = null;
}
}
通道方法按原有 Dart 调用逐项映射:
| Dart 方法 | OHOS 原生行为 |
|---|---|
initScanner | 标记兼容层已初始化并返回 true |
isPDASupported | 返回 false,等待具体厂商桥接 |
getPDAModel | 读取 deviceInfo.productModel |
getPlatformVersion | 返回 OHOS + deviceInfo.osFullName |
navigateToSystemHome | 通过 Want action 返回系统桌面 |
errorSound | 保留方法兼容性,暂不模拟厂商蜂鸣 |
初始化方法返回 true 表示“通道和兼容层可用”,不表示“设备一定有扫码硬件”。
这两个语义必须分开:
case 'initScanner':
this.initialized = true;
this.sendLog('INFO', 'OpenHarmony compatibility layer initialized');
result.success(true);
return;
case 'isPDASupported':
result.success(false);
return;
五、设备信息为什么不能直接复用 Android 页面
原示例页面使用 device_info_plus 读取 Android Build 信息。OHOS 上继续调用
Android 信息接口只会得到“不支持”的结果,因此兼容层直接使用 OHOS 的
deviceInfo:
import { deviceInfo } from '@kit.BasicServicesKit';
case 'getPDAModel':
result.success(deviceInfo.productModel || 'OHOS');
return;
case 'getPlatformVersion':
result.success(`OHOS ${deviceInfo.osFullName}`);
return;
示例页面的 TargetPlatform 分支也补充了 OHOS:
TargetPlatform.ohos => <String, dynamic>{
'platform': 'OHOS',
},
这样页面至少可以区分“Android Build 信息”和“OHOS 设备信息”,不会把 OHOS
设备错误地显示成 Fuchsia。
六、系统桌面跳转:使用 Want,而不是 Android Intent
Android 原库通过 Intent.ACTION_MAIN 和 CATEGORY_HOME 返回桌面。OHOS 使用
Want 表达同样的动作:
private navigateToSystemHome(): void {
const context = this.uiAbilityContext;
if (context === null) {
return;
}
const want = new Want();
want.action = 'ohos.want.action.home';
context.startAbility(want).catch((error) => {
this.sendLog('ERROR', `Unable to navigate home: ${String(error)}`);
});
}
这里的 uiAbilityContext 不能省略。只保存 ApplicationContext 时,当前 Ability
可能已经分离,跳转行为会变得不稳定。页面返回键调用
PdaUtils.navigateToSystemHome() 后,Dart 层不需要知道底层是 Intent 还是
Want。
七、音频资源为什么要延迟加载
原库在 init() 中加载两个 WAV 文件:
var resource = await rootBundle.load(
'packages/pda_scanner/assets/audio/scan_success.wav',
);
_scanSuccessAudioPlayer.setSource(
BytesSource(resource.buffer.asUint8List()),
);
这对 Android PDA 可以接受,但在 OHOS 虚拟机、权限尚未完成或测试环境下,启动
阶段会产生额外的 audioplayers 原生初始化和定时器。适配后改为第一次真正
播放提示音时才加载:
static Future<void> _loadScanAudioPlayer() async {
if (_audioInitialized) {
return;
}
// load success and failure WAV resources
_audioInitialized = true;
}
successSoundHumanVoice() 和 errorSoundHumanVoice() 会先等待资源加载,再调用
resume()。这样既保留原来的提示音行为,也让示例启动和自动化测试只依赖真正的
页面初始化。
原生 errorSoundDudu() 目前只返回成功,不伪造 OHOS 蜂鸣器能力;失败人声仍由
Dart audioplayers 播放。
八、厂商扫码桥接的预留设计
Android 的 CodeEmitterManager 负责把不同厂商广播统一发送到 Dart。OHOS 兼容
层没有绑定任何具体厂商 SDK,因此不会监听不存在的系统广播。后续接入具体 PDA
厂商时,桥接位置非常明确:
private emitBarcode(barcode: string): void {
if (this.channel === null) {
return;
}
this.channel.invokeMethod('sendBarcodeToFlutter', barcode);
}
厂商 SDK 的回调只需要调用 emitBarcode,Dart 端已有的
sendBarcodeToFlutter 分支就会把条码分发给所有 tag:
case 'sendBarcodeToFlutter':
final String? barcodeContent = call.arguments;
if (barcodeContent != null) {
_callback.forEach((tag, callback) {
callback.call(barcodeContent);
});
}
break;
这种结构让“OHOS 兼容层”和“OHOS 厂商扫码实现”可以分阶段交付:
- 第一阶段验证 Flutter 插件注册、通道、设备信息和生命周期。
- 第二阶段按具体设备接入 OHOS SDK。
- 第三阶段用真实扫码枪验证回调时序、重复条码过滤和提示音。
九、示例工程的兼容性修正
示例工程原来依赖 getwidget 4.0.0,在当前 Flutter OHOS 工具链上会因为
AppBarTheme 和 CardTheme 类型变化编译失败。适配时升级为:
getwidget: ^7.0.2
示例 widget 测试也从模板计数器测试改成真实页面测试:
- 注册
org.jerome/pda_scanner方法通道 mock。 - 调用
PdaUtils.init()。 - 渲染
MyApp。 - 验证 OHOS 测试设备型号和“未知,请测试。”能力状态。
测试中同时 mock xyz.luan/audioplayers,避免没有原生播放器时出现
MissingPluginException。示例界面本地渲染图如下:



所有评论(0)