文档扫描看起来像一个“调用摄像头再返回文件”的小功能,实际上 Android 和 iOS 的实现依赖了不同的系统能力:Android 侧调用 ML Kit Document Scanner,iOS 侧调用 VisionKit。把 flutter_doc_scanner 带到鸿蒙时,最容易犯的错误是把 HAP 构建成功误认为扫描能力已经完成。

本文以 flutter_doc_scanner 0.0.21 为例,记录我如何保留原有 Dart API、补齐 Flutter OH 插件入口、让鸿蒙端明确返回能力边界,并在 HarmonyOS API 26 真机上完成构建、安装、启动和按钮调用验证。当前分支可以运行 Demo,但鸿蒙系统扫描器 UI 尚未接入,所以真机最终显示 UNSUPPORTED。这个结果是适配过程的一部分,也是比返回空结果更可靠的结论。

适配仓库: oh-flutter/flutter_doc_scanner

适配分支: feat/ohos_flutter_doc_scanner_0.0.21

适配 TAG: 0.0.21-ohos.1

一、先看最终效果

图 1:HarmonyOS API 26 真机启动 Demo。页面显示插件返回的 OpenHarmony 3.47.4,状态为等待操作。

图 2:点击“扫描文档”后的真实结果。ArkTS 返回 UNSUPPORTED,Dart 层把错误码和原因显示在页面上。

验证点实测结果证据
Flutter OH 3.47 工程构建通过entry-default-signed.hap
签名、安装和启动通过图 1
getPlatformVersion返回 OpenHarmony 3.47.4图 1
getScanDocuments返回 UNSUPPORTED图 2
其他三个扫描方法已注册,返回明确不支持错误ArkTS 分支
鸿蒙扫描器 UI尚未接入适配边界

为了让每个入口都可以单独复现,我又在真机上依次点击了图片、PDF 和 URI 三个按钮:

图 3:调用 getScannedDocumentAsImages 后返回同一套 UNSUPPORTED 语义。

图 4:调用 getScannedDocumentAsPdf 后没有伪造 PDF 路径。

图 5:Android 专属的 getScanDocumentsUri 在鸿蒙端也会明确告知当前不可用。

图 6:图片入口返回后,页面最终显示错误码和原因,证明调用链已经回到 Dart 层。

二、这个库原来是怎么工作的

上游包的公共入口是 FlutterDocScanner。它提供四类调用:默认扫描、返回图片、返回 PDF 和返回 Android URI。Dart 层通过名为 flutter_doc_scanner 的 MethodChannel 把方法和参数传给原生端。

Dart FlutterDocScanner
  -> MethodChannel('flutter_doc_scanner')
  -> Android ML Kit / iOS VisionKit
  -> 图片路径、PDF 路径或 URI

本次适配先保留 Dart API 和通道名称,再新增 OHOS 插件实现。OHOS 端没有把 Android 的 ML Kit 或 iOS 的 VisionKit 直接搬过来,也没有伪造一个空的扫描结果,而是把当前能力状态通过标准错误返回给 Dart 层。

三、实际环境

Flutter 鸿蒙环境搭建不在本文重复展开,直接参考 Flutter OH 环境搭建指南。

组件版本
Flutter OH3.47.4-ohos-1.0.4
Dart3.13.3
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试设备HarmonyOS 7.0.0.105,API 26
目标库flutter_doc_scanner 0.0.21
flutter --version
hdc list targets

四、确认上游版本并同步到 AtomGit

上游包地址是 GitHub shirsh94/flutter_doc_scanner,版本 0.0.21 使用 MIT License。适配前我在 AtomGit 目标组织中搜索了同名库,没有找到可以直接使用的 OHOS 实现,因此从上游基线开始增加平台目录。

仓库初始适配提交为 3360835,完成后分支最新提交为 b5efbd9。

git clone https://atomgit.com/oh-flutter/flutter_doc_scanner.git
cd flutter_doc_scanner
git log --oneline -5
git switch feat/ohos_flutter_doc_scanner_0.0.21
git remote -v

先确认仓库、版本和远程地址,再开始改代码,后面出现编译问题时才容易区分是上游问题、工具链问题还是本次改动引入的问题。

五、创建适配分支并检查工程结构

git switch -c feat/ohos_flutter_doc_scanner_0.0.21
git branch --show-current

关键目录如下:

flutter_doc_scanner/
├── lib/                                  # 原有 Dart API 和 MethodChannel
├── ohos/
│   ├── index.ets                         # OHOS 插件导出入口
│   └── src/main/ets/components/plugin/
│       └── FlutterDocScannerPlugin.ets   # MethodChannel 实现
├── example/lib/main.dart                  # 真机验证 Demo
├── example/ohos/                         # Flutter OH 宿主工程
├── pubspec.yaml                          # 声明 ohos 平台
└── README.OpenHarmony_CN.md              # 鸿蒙适配说明

pubspec.yaml 中的关键声明是:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: FlutterDocScannerPlugin

pluginClass 必须和 ohos/index.ets 导出的类名一致,否则 Dart 端能编译,运行时却不会注册插件。

六、适配过程:先对齐契约,再写 ArkTS

我先从 lib/flutter_doc_scanner.dart 和 lib/flutter_doc_scanner_method_channel.dart 读取现有接口,没有改动公开方法名和参数:

Dart 方法参数OHOS 本次策略
getPlatformVersion无返回 OpenHarmony 3.47.4
getScanDocumentspage返回 UNSUPPORTED
getScannedDocumentAsImages页数、格式、质量、单图模式返回 UNSUPPORTED
getScannedDocumentAsPdfpage返回 UNSUPPORTED
getScanDocumentsUripage返回 UNSUPPORTED

这里有一个容易忽略的区别:null 是“调用完成但没有结果”的正常数据形态,常用于用户取消扫描;UNSUPPORTED 是“当前平台没有这项能力”的错误形态。两者混用会让业务层把“鸿蒙尚未适配”误认为“用户取消”。

OHOS 插件骨架使用 Flutter OH 的 FlutterPlugin、MethodCallHandler 和 MethodChannel:

onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel?.setMethodCallHandler(null);
  this.channel = new MethodChannel(
    binding.getBinaryMessenger(),
    'flutter_doc_scanner',
  );
  this.channel.setMethodCallHandler(this);
}

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  this.channel?.setMethodCallHandler(null);
  this.channel = null;
}

注册时使用的通道字符串必须和 Dart 层完全相同。解绑时清理 handler,避免引擎重复创建后出现重复回调。

核心方法分支如下:

case 'getScanDocuments':
case 'getScannedDocumentAsImages':
case 'getScannedDocumentAsPdf':
case 'getScanDocumentsUri':
  result.error(
    'UNSUPPORTED',
    'Document scanning is not available on OpenHarmony yet.',
    null,
  );
  return;

result.error 会被 Dart MethodChannel 捕获为 PlatformException,插件 Dart 实现再把它转换成 DocScanException。Demo 只捕获 DocScanException 并展示 code 和 message,所以真机页面能看到 UNSUPPORTED,而不是一串难以判断的空值。

七、解决两个构建配置问题

第一次构建时,Hvigor 报工程 compatibleSdkVersion 为 18,但依赖的 @ohos/flutter_ohos 要求最低 24。示例工程改成 API 26 后继续构建:

{
  "name": "default",
  "compatibleSdkVersion": "26.0.0",
  "targetSdkVersion": "26.0.0",
  "runtimeOS": "HarmonyOS"
}

第二个错误是本机调试签名中的 bundleName 与示例的 com.example.flutter_doc_scanner_example 不一致。为了使用本机 DevEco 调试签名,我把 Demo 的 AppScope/app.json5 统一成 com.example.flutter_oh_demo,并用这个包名启动。证书、p12、p7b 和密码没有提交到仓库,读者应在自己的 DevEco Studio 中生成调试签名。

八、通过 AtomGit 引入适配版

在另一个 Flutter OH 应用中使用时,建议锁定 TAG:

dependencies:
  flutter_doc_scanner:
    git:
      url: https://atomgit.com/oh-flutter/flutter_doc_scanner.git
      ref: 0.0.21-ohos.1
flutter pub get

然后在 pubspec.lock 检查 source: git、AtomGit 地址和 resolved-ref,确认没有解析到 pub.dev 的同名版本。

九、构建、安装和真机验证

cd example
flutter pub get
flutter analyze
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo

构建输出为 ✓ Built build/ohos/hap/entry-default-signed.hap.,随后 hdc 报告安装成功并启动 Ability。进入页面后先看到图 1,再点击“扫描文档”,得到图 2。

Dart 单元测试在本机还受到 Flutter SDK 缓存混用影响,测试进程报告 Invalid kernel binary format version (expected 130, found 138),没有产生有效测试结果。 flutter analyze 可以完成,只有上游 Web 实现使用 dart:html 的弃用提示。这个错误属于测试运行时缓存与 SDK 不一致,不能当作 ArkTS 适配失败;在全新、只使用 Flutter OH 3.47.4 的环境中应重新执行测试。

十、适配结果和当前边界

层次已完成内容尚未完成内容
Dart API保留原方法、模型和 DocScanException无
MethodChannel通道名和方法名全部对齐无
OHOS 插件注册、版本查询、错误回传真正的扫描 UI
DemoAPI 26 真机启动和按钮调用拍照、裁剪、生成图片/PDF
文档README.OpenHarmony_CN.md、CHANGELOG无

Android 的 ML Kit 和 iOS 的 VisionKit 不能通过改一个平台声明就变成鸿蒙能力。下一步如果要完成真正扫描,需要选定 HarmonyOS 的相机、图像处理和文件导出 API,补充相机权限,处理前后台切换和用户取消,并为图片、PDF、URI 三种返回结果分别做真机回归。当前提交明确告诉使用者“这个能力还没有接入”,比让业务误以为扫描成功更容易维护。

十一、FAQ

Q1:为什么 HAP 构建成功,页面仍然显示不支持?

  • 现象: HAP 可以签名、安装并启动,但点击扫描后显示 UNSUPPORTED。
  • 原因: 当前 OHOS ArkTS 端只完成插件注册和错误语义,没有接入系统扫描器 UI。
  • 解决方法: 保留明确错误,后续接入真正的 HarmonyOS 扫描实现后再替换对应分支。
  • 验证结果: 图 2 证明错误从 ArkTS 经 MethodChannel 到达 Dart 页面。

Q2:为什么不能直接返回 null?

  • 现象: 如果 OHOS 端返回 null,Dart 业务很容易把它当成用户取消。
  • 原因: null 是一次合法调用没有结果,UNSUPPORTED 才是能力不存在。
  • 解决方法: 四个扫描方法统一调用 result.error('UNSUPPORTED', ...)。
  • 验证结果: Demo 显示错误码和英文原因,状态可追踪。

Q3:为什么第一次构建提示 compatibleSdkVersion 18?

  • 现象: Hvigor 报 @ohos/flutter_ohos 要求最低兼容版本 24。
  • 原因: 示例工程沿用了旧的 18 配置。
  • 解决方法: 将示例产品的 compatible 和 target SDK 都改为 26。
  • 验证结果: 重新执行 flutter build hap --debug 通过。

Q4:为什么签名会报 bundleName 不一致?

  • 现象: SignHap 报 generated SigningConfigs 与 app.json5 的 bundleName 不匹配。
  • 原因: 本机调试签名绑定了 com.example.flutter_oh_demo,示例仍是另一个包名。
  • 解决方法: 使用自己的 DevEco 调试签名,并让本地 AppScope/app.json5 与签名对应。
  • 验证结果: 签名 HAP 生成、安装和启动均通过。

十二、总结

这次适配保留了 flutter_doc_scanner 0.0.21 原有 Dart 接口和 flutter_doc_scanner 通道,在 OHOS 侧增加了标准 FlutterPlugin 注册、版本查询和四个扫描方法的明确错误回传。Demo 通过 Flutter OH 3.47.4 构建成签名 HAP,并在 HarmonyOS API 26 真机上完成安装、启动和点击验证。

当前成果是“可构建、可安装、接口行为清楚”的鸿蒙适配基线,不代表鸿蒙扫描器已经完成。真正接入扫描能力时,应继续补充系统 API、权限、文件导出、取消流程和图片/PDF 结果回归。适配代码和说明都在 AtomGit 适配分支,复现时建议锁定 0.0.21-ohos.1 TAG。

十三、参考资料和仓库地址

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐