将 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. 管好内存和资源

  • 用完就释放: releaseScanResultReleaseImageAnalyzer 函数里的释放操作必须做到位,相机句柄、分析器实例、图像 Buffer 一个都不能漏,这是防止内存泄漏的关键。
  • 安全地传递上下文: setNativeContext 的实现要特别注意生命周期管理,确保从 Flutter 侧传过来的 context 在原生层使用时始终有效。

4. 利用好调试工具

  • 鸿蒙的 Hiview 日志系统很好用。像示例里那样打好 LOGILOGE 日志,在 DevEco Studio 的 HiLog 窗口里过滤 FlutterBarcodeScanner 标签,跟踪原生层的问题会方便很多。

五、总结与接下来的路

通过上面的步骤,我们基本上完成了 flutter_barcode_scanner 插件向鸿蒙平台的深度迁移。这次实践的核心思路是绕开传统的 MethodChannel,通过 FFI 直接调用鸿蒙的原生 C++ API。这条路虽然一开始走起来有点费力,但带来的性能优势和对系统能力的直接掌控是值得的。

这种模式不仅仅适用于扫码插件,对于其他需要调用复杂系统功能(比如传感器、高级图形处理、专用 AI 能力)的 Flutter 插件来说,也是一个可行的技术参考。

当然,目前这条路还不算平坦:

  1. 工具链还在成长: Flutter for OpenHarmony 毕竟还在早期,插件开发、调试、打包的自动化工具还不够完善,很多事需要手动处理。
  2. 需要社区合力: 单打独斗效率低。如果能有更多开发者分享不同类别插件的适配经验,形成一些“最佳实践”,对整个生态会大有裨益。
  3. 期待官方支持: 最理想的状况,还是未来 Flutter 官方或 OpenHarmony 社区能推出标准的插件平台通道鸿蒙实现,把适配成本降到最低。

这次深度适配至少证明了一件事:把 Flutter 成熟的插件生态迁移到鸿蒙系统,在技术上是完全可行的。希望这个具体的案例能为你自己的项目带来一些启发,也期待看到更多优秀的 Flutter 插件在鸿蒙生态里焕发生机。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐