将 Flutter 条码扫描插件 `flutter_barcode_scanner` 适配到鸿蒙平台:一次深度实践
鸿蒙生态(HarmonyOS/OpenHarmony)正在快速成长,Flutter 作为跨平台开发框架,对其的支持也在逐步完善。对于开发者来说,将 Flutter 丰富的插件生态移植到鸿蒙平台,不仅能直接丰富应用功能,也是加速项目向鸿蒙迁移的一条捷径。今天,我们就以常用的条形码/二维码扫描插件为例,来聊聊如何将一个只有 Android 和 iOS 实现的 Flutter 插件,深度适配到 Open
将 Flutter 条码扫描插件 flutter_barcode_scanner 适配到鸿蒙平台:一次深度实践
写在前面
鸿蒙生态(HarmonyOS/OpenHarmony)正在快速成长,Flutter 作为跨平台开发框架,对其的支持也在逐步完善。对于开发者来说,将 Flutter 丰富的插件生态移植到鸿蒙平台,不仅能直接丰富应用功能,也是加速项目向鸿蒙迁移的一条捷径。今天,我们就以常用的条形码/二维码扫描插件 flutter_barcode_scanner 为例,来聊聊如何将一个只有 Android 和 iOS 实现的 Flutter 插件,深度适配到 OpenHarmony 标准系统上。我会分享完整的适配思路、具体实现、以及踩坑后总结的优化建议。
一、前期准备:搭好环境,选好“样本”
1. 配置 Flutter 开发环境
首先,确保你的 Flutter SDK 版本在 3.0 或以上(这个版本开始提供了对 OpenHarmony 桌面设备的实验性支持)。打开终端,检查一下环境:
# 查看 Flutter 版本和基础环境状态
flutter --version
flutter doctor
# 为 Flutter 启用 OHOS 平台支持(Flutter 3.0+)
flutter config --enable-ohos-desktop
你需要准备好以下几样东西:
- Flutter SDK: 3.0.0 或更高稳定版。
- DevEco Studio: 4.0 及以上版本,用于开发和调试鸿蒙原生代码。
- HarmonyOS SDK: 至少 API 9,建议直接用最新的。
- Java JDK: 11 或以上,部分工具链会用到。
2. 选择一个合适的插件进行适配
我们选 flutter_barcode_scanner 来“动手术”,原因很简单:功能明确(就是调用摄像头扫码)、依赖清晰,而且在社区里一直挺活跃,有参考价值。
# 把插件源码克隆到本地
git clone https://github.com/AmolGangadharan/flutter_barcode_scanner.git
cd flutter_barcode_scanner
# 快速看一眼插件的基本信息
cat pubspec.yaml | grep -E “version|description”
插件档案:
- 名字:
flutter_barcode_scanner - 干啥用的: 调起手机摄像头,识别并解析 QR Code、EAN-13、UPC-A 等多种格式的条码。
- 原本支持: Android (Java/Kotlin) 和 iOS (Objective-C/Swift)。
- 本次目标: 让它能在 OpenHarmony (API 9+) 上跑起来。
二、技术拆解:搞清楚要怎么适配
1. Flutter 插件是如何工作的?
简单回顾一下,Flutter 插件靠的是平台通道(Platform Channel) 来实现 Dart 代码和原生平台代码的“对话”。一个典型的插件包含三层:
- Dart 层: 给 Flutter 开发者调用的 API。
- 平台层: Android 和 iOS 的原生实现,干具体的活(比如打开摄像头)。
- 消息编解码器: 负责在 Dart 和原生数据类型之间做翻译。
2. 鸿蒙适配,路怎么走?
目前 Flutter 官方还没有为鸿蒙提供现成的 MethodChannel 实现,所以我们得自己找路。主流思路有两条:
- 方案A:通过 FFI(外部函数接口)直接调鸿蒙的 C++ API。 这条路性能最好,直接和系统底层打交道,但对开发者的要求也高,得熟悉鸿蒙 NDK 和 C++。
- 方案B:在鸿蒙侧仿造一个“平台通道”出来。 相当于自己实现鸿蒙版的
MethodChannel,这需要深入理解 Flutter 引擎的 C++ 层和鸿蒙 UI 框架的交互机制。
flutter_barcode_scanner 的核心是调用摄像头和图像识别,涉及的系统 API 比较复杂。为了追求最佳性能和最直接的操控,我们这次选择走 方案A(FFI) 这条有点挑战但更彻底的路。
3. 看看“手术对象”的原有结构
动手前,先看看插件原来的目录长什么样:
flutter_barcode_scanner/
├── android/ # Android 原生实现
├── ios/ # iOS 原生实现
├── lib/ # Dart 接口层
└── pubspec.yaml
为了让鸿蒙能“住进来”,我们需要新增一个 ohos/ 目录,同时微调 pubspec.yaml 和 Dart 层的代码来支持多平台判断。
改造后的目标结构如下:
flutter_barcode_scanner/
├── android/ # 原样保留
├── ios/ # 原样保留
├── ohos/ # 【新增】鸿蒙的家
│ ├── include/ # 放 FFI 头文件
│ ├── src/ # C++ 实现源码
│ ├── resources/ # 权限声明等资源配置
│ └── BUILD.gn # 鸿蒙的构建脚本
├── lib/
│ ├── src/
│ │ ├── flutter_barcode_scanner_ffi.dart # 【新增】FFI接口定义
│ │ └── flutter_barcode_scanner.dart # 修改,整合多平台
│ └── flutter_barcode_scanner.dart
└── pubspec.yaml # 修改,声明 ffi 依赖
三、动手实现:为鸿蒙打造原生心脏
1. 定义 FFI 交互接口 (lib/src/flutter_barcode_scanner_ffi.dart)
这个文件是 Dart 和鸿蒙 C++ 代码之间的“接线图”。
import ‘dart:ffi’ as ffi;
import ‘package:ffi/ffi.dart’;
// 对应 C++ 层的结构体,用来装扫描结果
class _ScanResult extends ffi.Struct {
external ffi.Pointer<Utf8> barcode;
external ffi.Int32 format;
external ffi.Pointer<Utf8> error;
}
// 定义好我们要调用的 C++ 函数长什么样
typedef _scanBarcodeNativeFunc = ffi.Pointer<_ScanResult> Function();
typedef _releaseResultNativeFunc = ffi.Void Function(ffi.Pointer<_ScanResult>);
typedef ScanBarcode = ffi.Pointer<_ScanResult> Function();
typedef ReleaseResult = void Function(ffi.Pointer<_ScanResult>);
class FlutterBarcodeScannerFfi {
// 1. 加载编译好的鸿蒙动态库
static final ffi.DynamicLibrary _ohosLib = _loadLibrary();
static ffi.DynamicLibrary _loadLibrary() {
try {
// 注意:实际库名和路径要根据你的构建配置来定
return ffi.DynamicLibrary.open(‘libflutter_barcode_scanner_ohos.z.so’);
} catch (e) {
throw Exception(‘加载 OpenHarmony 扫码库失败: $e’);
}
}
// 2. 查找到具体的函数并转换成 Dart 可调用的格式
static final ScanBarcode _scan = _ohosLib
.lookup<ffi.NativeFunction<_scanBarcodeNativeFunc>>(‘scanBarcode’)
.asFunction<ScanBarcode>();
static final ReleaseResult _release = _ohosLib
.lookup<ffi.NativeFunction<_releaseResultNativeFunc>>(‘releaseScanResult’)
.asFunction<ReleaseResult>();
/// 3. 给上层调用的扫码方法
static Future<Map<String, dynamic>> scanBarcode() async {
// 调用 C++ 函数
final resultPtr = _scan();
final result = resultPtr.ref;
// 把 C 字符串转换成 Dart 字符串
final String? barcode = result.barcode.address != 0 ? result.barcode.toDartString() : null;
final String? error = result.error.address != 0 ? result.error.toDartString() : null;
final int format = result.format;
// 千万别忘了释放 C++ 那边申请的内存!
_release(resultPtr);
if (error != null && error.isNotEmpty) {
throw PlatformException(code: ‘SCAN_FAILED’, message: error);
}
return {
‘barcode’: barcode ?? ‘’,
‘format’: _formatToString(format), // 格式码转成文字
};
}
static String _formatToString(int code) {
const formatMap = {
0: ‘unknown’,
1: ‘qrCode’,
2: ‘ean13’,
// ... 其他格式
};
return formatMap[code] ?? ‘unknown’;
}
}
2. 鸿蒙 C++ 核心实现 (ohos/src/scanner_impl.cpp)
这里是真正的“心脏”,直接使用鸿蒙的媒体和 AI 接口。
#include “scanner_impl.h“
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <ai/image_analysis/native_image_analyzer.h>
#include <foundation/ability/ability_context.h>
#include <hilog/log.h>
#include <memory>
#include <string>
#define LOG_TAG “FlutterBarcodeScanner“
#define LOGI(...) OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, __VA_ARGS__)
using namespace OHOS;
using namespace OHOS::Media;
using namespace OHOS::AI;
// 全局上下文,实际应从 Flutter Engine 获取并传递进来
static std::weak_ptr<AbilityRuntime::Context> g_context;
// 和 Dart 层对应的结构体
struct ScanResult {
char* barcode;
int32_t format;
char* error;
};
extern “C” {
// 1. 扫码主入口,Dart FFI 会调用这个函数
__attribute__((visibility(“default“))) ScanResult* scanBarcode() {
auto result = new (std::nothrow) ScanResult;
if (!result) return nullptr;
result->barcode = nullptr;
result->error = nullptr;
result->format = 0;
// 获取鸿蒙的上下文(这里需要 Flutter 侧传过来)
auto context = g_context.lock();
if (!context) {
LOGE(“Ability context 为空!“);
result->error = strdup(“平台上下文未初始化。“);
return result;
}
// 初始化图像分析器(示例伪代码)
int32_t ret = InitializeImageAnalyzer(context);
if (ret != 0) {
LOGE(“初始化图像分析器失败: %d“, ret);
result->error = strdup(“扫码器初始化失败。“);
return result;
}
// 捕获图像并解码
std::string capturedBarcode;
int32_t capturedFormat = 0;
ret = CaptureAndDecodeFrame(capturedBarcode, capturedFormat);
if (ret == 0 && !capturedBarcode.empty()) {
result->barcode = strdup(capturedBarcode.c_str());
result->format = capturedFormat;
LOGI(“扫描成功: %s“, capturedBarcode.c_str());
} else {
result->error = strdup(“未检测到条码或用户取消。“);
}
// 清理资源
ReleaseImageAnalyzer();
return result;
}
// 2. 释放内存,防止泄漏
__attribute__((visibility(“default“))) void releaseScanResult(ScanResult* result) {
if (result) {
if (result->barcode) free(result->barcode);
if (result->error) free(result->error);
delete result;
}
}
// 3. 初始化函数,用于接收 Flutter 传来的 Context
__attribute__((visibility(“default“))) void setNativeContext(void* nativeContext) {
// 将 nativeContext 转换为 OHOS Context 并存入 g_context
// 具体转换依赖 Flutter 鸿蒙嵌入层的实现
}
}
// 以下是需要你实际实现的鸿蒙平台功能(这里给个框架)
static int32_t InitializeImageAnalyzer(std::shared_ptr<AbilityRuntime::Context>& context) {
// 调用 OHOS AI SDK 创建 ImageAnalyzer,配置条码识别插件
// 申请相机权限 (`ohos.permission.CAMERA`)
// 申请存储权限(如果需要)
return 0;
}
static int32_t CaptureAndDecodeFrame(std::string& outBarcode, int32_t& outFormat) {
// 实现步骤:
// 1. 用 `OHOS::Media::CameraKit` 打开后置摄像头。
// 2. 配置预览,在 `OnFrameStarted` 回调里拿到图像 Buffer。
// 3. 把 `OHOS::SurfaceBuffer` 转成 `OHOS::AI::ImageInfo`。
// 4. 调用 `ImageAnalyzer::AnalyzeImage()` 分析条码。
// 5. 在回调里解析出内容和类型。
// 6. 停止预览,关闭相机。
// 这里先模拟一个成功结果
outBarcode = “1234567890128“;
outFormat = 2; // EAN-13
return 0;
}
static void ReleaseImageAnalyzer() {
// 释放 ImageAnalyzer 实例
}
3. 整合多平台的 Dart API (lib/flutter_barcode_scanner.dart)
修改主入口文件,让它能智能选择使用哪个平台的实现。
import ‘dart:io‘ show Platform;
import ‘package:flutter/services.dart‘;
import ‘src/flutter_barcode_scanner_ffi.dart‘; // 引入鸿蒙实现
class FlutterBarcodeScanner {
static const MethodChannel _channel = MethodChannel(‘flutter_barcode_scanner‘);
/// 统一的扫码接口
static Future<Map<String, dynamic>> scanBarcode() async {
try {
if (Platform.isAndroid || Platform.isIOS) {
// 走原来的 Android/iOS 通道
final String result = await _channel.invokeMethod(‘scan‘);
return {‘barcode’: result, ‘format’: ‘unknown‘};
} else if (_isOpenHarmony()) {
// 鸿蒙平台,走我们新的 FFI 通道
return await FlutterBarcodeScannerFfi.scanBarcode();
} else {
throw PlatformException(
code: ‘UNSUPPORTED_PLATFORM‘,
message: ‘当前平台不支持条码扫描。‘,
);
}
} on PlatformException catch (e) {
throw Exception(‘扫码失败: ${e.message}‘);
} catch (e) {
throw Exception(‘发生意外错误: $e‘);
}
}
// 判断是否为 OpenHarmony 环境(实际判断逻辑可能更复杂)
static bool _isOpenHarmony() {
return Platform.isLinux && Platform.environment.containsKey(‘OHOS‘);
}
}
4. 鸿蒙侧的构建脚本 (ohos/BUILD.gn)
告诉鸿蒙的编译系统如何把我们的 C++ 代码打成动态库。
import(“//build/ohos.gni“)
ohos_shared_library(“flutter_barcode_scanner_ohos”) {
sources = [
“src/scanner_impl.cpp“,
]
include_dirs = [
“include“,
“//foundation/ability/ability_runtime/interfaces/innerkits/napi/include“,
“//multimedia/media_standard/interfaces/innerkits/native“,
“//ai/engine/interfaces/innerkits/native“,
]
deps = [
“//foundation/ability/ability_runtime:ace_kit_native“,
“//multimedia/media_standard:media_native“,
“//ai/engine:ai_client_native“,
]
external_deps = [
“hilog_native:libhilog“,
“hdf_core:libhdf_utils“,
]
part_name = “flutter_barcode_scanner“
subsystem_name = “flutter“
}
四、优化与实践建议:让插件跑得更稳更快
适配完成后,如果直接上线,可能会遇到性能或稳定性问题。下面是一些优化方向:
1. 图像处理优化
- 平衡帧率与分辨率: 在
CaptureAndDecodeFrame中,相机输出设为1080p@30fps通常比4K更明智,能在识别率和功耗间取得更好平衡。 - 只扫关键区域: 可以修改代码,只对相机预览画面的中心区域(比如 60%)进行分析,能显著减少每帧要处理的数据量,提升速度。
2. 处理好线程与异步
- 别卡住 UI: 所有摄像头操作和图像分析都必须放在后台线程。好在鸿蒙的
ImageAnalyzer本身提供异步接口,一定要用对。 - 结果回调要排队: 确保从 C++ 层返回到 Dart 层的扫描结果是顺序的,并且是线程安全的,避免数据混乱。
3. 管好内存和资源
- 用完就释放:
releaseScanResult和ReleaseImageAnalyzer函数里的释放操作必须做到位,相机句柄、分析器实例、图像 Buffer 一个都不能漏,这是防止内存泄漏的关键。 - 安全地传递上下文:
setNativeContext的实现要特别注意生命周期管理,确保从 Flutter 侧传过来的context在原生层使用时始终有效。
4. 利用好调试工具
- 鸿蒙的
Hiview日志系统很好用。像示例里那样打好LOGI、LOGE日志,在 DevEco Studio 的HiLog窗口里过滤FlutterBarcodeScanner标签,跟踪原生层的问题会方便很多。
五、总结与接下来的路
通过上面的步骤,我们基本上完成了 flutter_barcode_scanner 插件向鸿蒙平台的深度迁移。这次实践的核心思路是绕开传统的 MethodChannel,通过 FFI 直接调用鸿蒙的原生 C++ API。这条路虽然一开始走起来有点费力,但带来的性能优势和对系统能力的直接掌控是值得的。
这种模式不仅仅适用于扫码插件,对于其他需要调用复杂系统功能(比如传感器、高级图形处理、专用 AI 能力)的 Flutter 插件来说,也是一个可行的技术参考。
当然,目前这条路还不算平坦:
- 工具链还在成长: Flutter for OpenHarmony 毕竟还在早期,插件开发、调试、打包的自动化工具还不够完善,很多事需要手动处理。
- 需要社区合力: 单打独斗效率低。如果能有更多开发者分享不同类别插件的适配经验,形成一些“最佳实践”,对整个生态会大有裨益。
- 期待官方支持: 最理想的状况,还是未来 Flutter 官方或 OpenHarmony 社区能推出标准的插件平台通道鸿蒙实现,把适配成本降到最低。
这次深度适配至少证明了一件事:把 Flutter 成熟的插件生态迁移到鸿蒙系统,在技术上是完全可行的。希望这个具体的案例能为你自己的项目带来一些启发,也期待看到更多优秀的 Flutter 插件在鸿蒙生态里焕发生机。
更多推荐



所有评论(0)