识别链路怎么接、跨端 JSON 契约怎么逐字节对齐、系统不给置信度怎么办——这篇文章用一个跑在鸿蒙真机上的 Flutter 插件工程,把这三个问题一次讲透。

仓库地址:https://atomgit.com/lqjmac/flutter_ohos_ocr
版本地址:https://atomgit.com/CPF-Flutter/flutter_flutter/tree/oh-3.41.9-release
三方库框架:https://atomgit.com/oh-Flutter

0. 环境与实测设备

项版本/型号
DevEco Studio6.1.0(HarmonyOS SDK 26.0.0 / API 26)
Flutter(鸿蒙版)3.41.9+ohos-1.0.2,Dart 3.11.0+
hvigor6.26.4(DevEco Studio 自带;命令行工具 6.21.2 解析不了 26.0.0 形式版本号)
flutter_ohos_ocr 插件1.0.1(本仓库,path 依赖)
测试35 项 Dart 单元测试 + 3 项 widget 测试 + 3 项真机集成测试
实测设备HUAWEI ALN-AL00,HarmonyOS 7.0.0.109(SP6C00E105R4P3)
识别引擎HarmonyOS Core Vision Kit(textRecognition,端侧离线)

1. 识别要上云?——这个问题是怎么找上我的

起因其实很简单。产品要做"拍一张小票、把金额抓出来",我第一反应跟大多数人一样朴素:Flutter 里做 OCR,不就是找个插件调个云服务吗?——百度 OCR 一个 key、腾讯云一个 secret,图片 base64 一传,结果一收,完事。动手之前我甚至已经把计费页面打开了。

退一步想才觉得不对劲:小票是最敏感的个人数据之一——在哪消费、买了什么、付了多少钱。为了识别几行字把它传到别人的服务器上,光是安全评审就过不去;何况断网场景(地库、仓库、飞机上)直接不可用。另一条路的门槛又被我想高了:自己嵌 TFLite 模型跑端侧识别,模型、前后处理、算子适配,一听就是一个月的活。

这时候翻 HarmonyOS 的 API 文档才发现,系统早就把这件事做完了:Core Vision Kit 里有一个 textRecognition,端侧离线识别图片文字,不需要密钥、不需要网络、不需要申请权限。所以这个题目突然从"怎么接云服务"变成了"怎么把系统能力桥接进 Flutter"——难点换了位置:

  • 三个入口(内存字节、rawfile、相册)怎么收敛成一条识别链路(这是核心);
  • 结果 JSON 怎么和系列的 KMP 版(kmp-ohos-ocr)逐字节一致,让跨端对账成立;
  • Core Vision Kit 不返回逐行置信度,这个缺失怎么诚实地处理——不伪造数据。

下面是干完之后的完整记录,包括我踩的每一个坑。

2. 效果展示

按老规矩,先给效果再讲原理。示例应用叫"Flutter OCR 实验室",底部三个 Tab:识别实验室、确定性引擎、契约与诊断,正好对应上面三件事。

第一页是识别实验室。顶部状态徽章"设备就绪"表示 initialize() 探测原生插件成功;页面里躺着一张内置示例图——一张用脚本生成的"门店收银小票"(ocr_sample.png,1000×1400,84.4 KB,下面标注了文件名、尺寸和体积)。点"识别内置示例":

在这里插入图片描述

识别结果卡片出现了:1000×1400 · 15 段 · 312 ms,来源标注 vision-on-device · 简体中文 · 平均置信度 未提供,下面是从"门店收银小票"到每一行商品与价格的编号列表。三个数字都值得停一下:15 段是小票上被识别出的全部文本行;312 ms是从字节进通道到结果回来的全程端侧耗时,图片没有离开过这台设备;**平均置信度"未提供"**不是 bug,是设计——系统不给这个数据,界面就明说,不编一个百分比出来(后文细说)。

第二页验证的是任意真实内容。这次不点内置示例,点"从相册选择",从图库里挑了另一张应用截图:

在这里插入图片描述

系统相册拉起、选中、读字节、识别,一条龙:1260×2720 · 21 段 · 507 ms,连截图里小字号的状态栏时间"04:47"和引擎版本号"3.1.0-rc02-ohos.1"都抓出来了。整个过程没有弹过任何权限对话框——鸿蒙的相册选择器是"安全访问图库",用户选中哪张、应用只能读哪张,按次授权,连 READ_IMAGEVIDEO 权限都不用申请。

第二、三个 Tab 是这套插件的后台:确定性引擎页跑固定语料、置信度过滤和 6 项自检;契约与诊断页把原生返回的 JSON 和 Dart 侧手写的 JSON 逐字节对账,两边长度 348 字符、内容一致,页面下方还有实时滚动的诊断日志。真机集成测试(flutter test integration_test/plugin_test.dart -d <device-id>)验证的就是这三件事:插件可用、JSON 逐字节一致、内置小票识别出"收银小票"与价格。

3. 识别到底从哪来:一条完全离线的链路

写代码之前得先把原理捋好。

Core Vision Kit 的文字识别接口出奇地克制——给它一个 PixelMap,还你一个 TextRecognitionResult:

三个入口,一条河:
  recognizeBytes(bytes)      ── image.createImageSource(bytes) ─┐
  recognizeRawFile(name)     ── resourceManager.getRawFileContent ┤→ createPixelMap
  pickAndRecognize()         ── PhotoViewPicker → fileIo 读字节 ──┘
                                      │
                                      ▼
                    textRecognition.recognizeText({pixelMap})
                                      │
                                      ▼
              TextRecognitionResult.blocks[].lines[].value
                                      │
                    collectBlocks(拍平 / trim / 去重 / 置信度 0.0)
                                      ▼
                 OcrResult → 手写 JSON → MethodChannel → Dart

整条链路上没有任何网络请求——图片解码、识别、结果组装全部发生在设备本地。这也是"端侧"两个字的实际含义:不只是"能离线",而是架构上就没有出设备的路径。

置信度的诚实处理值得单独讲。我本来想让结果里带上每行的置信度、UI 渲染成百分比——翻文档发现 Core Vision Kit 只返回 TextBlock.value / TextLine.value 和坐标,根本不提供逐行置信度。两个选择:编一个漂亮的数(比如全填 0.95),或者老实填 0.0 并让 UI 显示"未提供"。我选了后者——一旦你在数据层撒一个谎,下游所有依赖置信度的逻辑(过滤、排序、展示)都在基座上造假。确定性演示引擎的场景仍然带 0.88~0.99 的置信度,但那是固定语料,用于演示过滤逻辑,与真实识别结果在 UI 上明确区分。

去重不是洁癖。OcrResult 的模型要求块文本唯一(这是和 KMP 版对齐的契约),而小票上出现两次 ¥0.00 是完全合法的输入——所以原生侧在收集阶段按首次出现去重。这不是丢数据:全文文本(fullText)保留原始重复,去重只发生在结构化的块列表里。

顺手记一笔性能账:1000×1400 的内置小票,README 记录的真机数据是原生耗时约 140~240 ms,这次截图跑出来 312 ms(不同批次波动);1260×2720 的大图 507 ms。全部是端侧推理时间,不吃流量、不排队、不要钱。代价在通道上:识别类方法会把原图字节再传回 Dart 一份做预览(imageBytes),大图等于过两遍通道——README 的已知限制里写了,调用前把图片压到 4~8 MB 以内。换句话说,瓶颈不在识别引擎,而在你怎么管理进出 Flutter 的字节。

其他路线对照一下:

方案识别位置特点
云服务 OCR云端要网络、要密钥、按量计费,图片出设备
ML Kit设备(Android/iOS)Flutter 官方插件没有鸿蒙实现
自研 TFLite/ONNX设备灵活,但模型、前后处理、算子适配全自己扛
PaddleOCR 移植设备模型体积大,鸿蒙侧需自行移植推理栈
Core Vision Kit(本库)设备系统自带,离线、免密钥、免权限,API 12+ 即可用
kmp-ohos-ocr设备同一系统的 Kotlin 入口,本库的契约对齐对象
纯 Dart 确定性引擎—(不识别)固定语料,离线演示与跨端对账基准

能力边界也要说清楚:Core Vision Kit 不提供文字方向和语言标签(插件参数里的 language 只是记录进结果的元数据,识别本身自动判语种);坐标信息(cornerPoints)暂未透出到 Dart,模型与 KMP 版保持一致;不支持 PDF、批量图片、实时相机流——这些留给宿主业务层。了解了这个边界,你就知道什么时候该用系统能力(单图、离线、隐私敏感),什么时候必须自己上模型(版面分析、表格还原、流式识别)。

4. 工程怎么搭:一个插件,两条链路,一份契约

先看整体结构。这是一个标准 Flutter 插件:根目录是可复用的 package(flutter_ohos_ocr),示例应用退到 example/,三页示例正好把插件的三层能力各演一遍:

flutter_ohos_ocr/
├─ lib/
│  ├─ flutter_ohos_ocr.dart           公共入口与导出
│  └─ src/
│     ├─ ohos_ocr.dart                MethodChannel 门面(OhosOcr)
│     ├─ ocr_engine.dart              确定性演示引擎(纯 Dart)
│     ├─ ocr_json.dart                手写 JSON 契约
│     ├─ ocr_result.dart / ocr_block.dart / ocr_language.dart
│     ├─ ocr_recognition.dart         识别结果 + 预览
│     ├─ ocr_log.dart                 诊断日志 / 自检
│     └─ ocr_bridge.dart              纯 Dart 桥接层
├─ ohos/                              # OpenHarmony HAR(插件平台实现)
│  ├─ index.ets / oh-package.json5
│  └─ src/main/ets/
│     ├─ components/plugin/FlutterOhosOcrPlugin.ets   通道分发
│     └─ ocr/
│        ├─ VisionOcr.ets             Core Vision Kit 端侧识别
│        ├─ OcrJson.ets               手写 JSON(与 Dart/Kotlin 逐字节一致)
│        ├─ OcrEngine.ets / OcrModels.ets
├─ example/                           # 三页示例 + 真机集成测试 + 鸿蒙宿主
├─ test/                              # 35 项 Dart 单元测试
├─ docs/screenshots/
├─ README.OpenHarmony.md / README.OpenHarmony_CN.md
└─ pubspec.yaml

在这里插入图片描述

这次设计最关键的决定有两个:

两条链路共用一个通道、一份模型。 通道上跑的不只是识别——result / filter / checks 三个方法走确定性演示引擎(KMP 版语料的 ArkTS 移植),recognizeBytes / recognizeRawFile / pickAndRecognize 三个方法走 Core Vision Kit。两边的输出共用同一份 OcrResult 模型和同一份 JSON 序列化规则,于是"契约与诊断"页才能做逐字节对账,真机集成测试才能断言"原生 JSON 与 Dart 348 字符逐字节一致"。一套模型两用,测试和演示全搭上了车。

手写 JSON,不用平台序列化器。 通道两端是 ArkTS 和 Dart,各自有 JSON.stringify / jsonEncode,但字段顺序、双精度格式、转义细节全是实现细节——想逐字节一致就得自己写。OcrJson.ets 的注释把动机写得很直白:输出是手工拼装的,字段顺序和格式保持与 Kotlin、Dart 实现逐字节相同。

通道链路一目了然:

Flutter UI
  └─ OhosOcr(lib/src/ohos_ocr.dart)
       │  MethodChannel('flutter_ohos_ocr')
       ▼
FlutterOhosOcrPlugin(ohos/src/main/ets/components/plugin)
  ├─ result / filter / checks    → OcrEngine + OcrJson(确定性引擎)
  └─ recognizeBytes / recognizeRawFile / pickAndRecognize
       └─ VisionOcr(Core Vision Kit 端侧识别)
            └─ {json, imageBytes, source, width, height, elapsedMs}

和空间音频那篇的插件一样,它不需要在 EntryAbility 里手动 attach:pubspec.yaml 声明了平台实现,Flutter 工具在宿主工程生成 GeneratedPluginRegistrant.ets,由它把 FlutterOhosOcrPlugin 注册进引擎。使用方看到的 API 是这样的:

final OhosOcr ocr = OhosOcr.instance;
if (await ocr.initialize()) {
  final OcrRecognition run = await ocr.recognizeRawFile('ocr_sample.png');
  print(run.result.fullText);   // 门店收银小票 …
  print(run.result.durationMs); // 原生耗时
}

下面把插件内部拆开讲:一条识别主链路、三个入口、一次结果整理、一份 JSON 契约,外加通道协议和可观测性。

5. 把代码讲透

5.1 识别主链路:三步解码,一次释放

VisionOcr.recognizeBytes 是所有入口的终点,核心就三步——建 ImageSource、解码 PixelMap、调 recognizeText:

// ohos/src/main/ets/ocr/VisionOcr.ets - recognizeBytes()
imageSource = image.createImageSource(VisionOcr.toArrayBuffer(bytes));
pixelMap = await imageSource.createPixelMap();
const info: image.ImageInfo = await pixelMap.getImageInfo();
const visionInfo: textRecognition.VisionInfo = { pixelMap: pixelMap };
const output: textRecognition.TextRecognitionResult =
  await textRecognition.recognizeText(visionInfo);
const blocks: OcrBlock[] = VisionOcr.collectBlocks(output);
const elapsed: number = Date.now() - started;
// ... 组装 OcrResult 与 VisionRun

几个我调试时才意识到的地方,写出来给后来人省时间:

Uint8Array 要复制成独立的 ArrayBuffer。 image.createImageSource 不收 Uint8Array,而 Flutter 编解码器交付的就是它。toArrayBuffer 把内容拷进一个新缓冲再交给 ImageSource——别为了省一次拷贝直接传 bytes.buffer,通道里过来的视图可能带 offset,截错一段解码就废了。

释放写在 finally 里。 PixelMap 和 ImageSource 都是持有原生内存的对象,识别成功要放,抛异常更要放:

} finally {
  if (pixelMap !== null) {
    await pixelMap.release();
  }
  if (imageSource !== null) {
    await imageSource.release();
  }
}

识别引擎一秒吃几张大图时,漏放一次就是几 MB 的原生内存——这种泄漏在 Flutter 侧完全不可见,只能等设备发烫了才发现。

language 只是元数据。 调用方传 OcrLanguage.chinese,它会被写进结果模型("language": "CHINESE"),但识别本身自动判断语种——截图里那张中英混排的截图就识别得很好。别指望这个参数能提高准确率,它不进引擎。

5.2 三个入口一条河

rawfile 和相册两个入口最终都汇到 recognizeBytes,各自的 peculiarities 值得展开:

rawfile 入口一行搞定——context.resourceManager.getRawFileContent(rawFileName) 返回字节,直接进主链路。这里的 context 是插件 onAttachedToEngine 拿到的应用上下文,不假设宿主挂在哪个 Ability 上。

相册入口是三个入口里唯一碰系统 UI 的:

// ohos/src/main/ets/ocr/VisionOcr.ets - pickAndRecognize()
const picker = new photoAccessHelper.PhotoViewPicker();
const options: photoAccessHelper.PhotoSelectOptions = {
  MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
  maxSelectNumber: 1,
};
const selection = await picker.select(options);
if (uris === null || uris === undefined || uris.length === 0) {
  return null;                       // 用户取消:返回 null,不是异常
}
const bytes: Uint8Array = await VisionOcr.readUri(uri);
return VisionOcr.recognizeBytes(bytes, VisionOcr.displayName(uri), language);

readUri 的姿势是标准三段式:fileIo.open(uri, READ_ONLY) → stat(fd) 按实际大小分配缓冲 → read → finally 里 close。两个礼仪:取消不是错误——用户关掉选择器返回 null,让调用方自己决定怎么处理;选择器是"安全访问图库"——按次授权、无需声明 READ_IMAGEVIDEO 权限、无需运行时授权弹窗,这是鸿蒙把隐私做成默认值的一个范例。展示名(displayName)从 URI 尾段解码而来,进了结果模型方便 UI 展示"你刚才选了哪张图"。

5.3 结果整理:collectBlocks 的四个动作

Core Vision Kit 返回的是"块 → 行"两级结构,collectBlocks 把它整理成扁平的 OcrBlock[]:

// ohos/src/main/ets/ocr/VisionOcr.ets - collectBlocks()
const blocks: OcrBlock[] = [];
const seen = new Set<string>();
for (let i = 0; i < textBlocks.length; i++) {
  const lines = textBlocks[i].lines ?? [];
  for (let j = 0; j < lines.length; j++) {
    const value: string = lines[j].value;
    if (value === null || value === undefined) {
      continue;
    }
    const text: string = value.trim();
    if (text.length === 0 || seen.has(text)) {
      continue;                      // 空行、跨块重复行:跳过
    }
    seen.add(text);
    blocks.push(new OcrBlock(text, 0.0));   // CVK 不给置信度:0.0,UI 显示"未提供"
  }
}

四个动作各有各的理由:拍平是因为 UI 关心的是"识别出了哪些行",不是版面结构;trim 是识别结果常带首尾空白;Set 去重服务契约约束(块文本唯一),小票上两次 ¥0.00 只保留首次——但 fullText 不动,原始重复都在;0.0 置信度是上面说过的诚实策略。另外它对 blocks / lines 为 null 的防御也顺手做了——CVK 对纯图形图片会返回空结构,不设防就是一次空指针。

5.4 手写 JSON:一份逐字节对得上的契约

OcrJson.ets 是整个插件里最"轴"的文件:不用 JSON.stringify,字段顺序、格式、转义全部手工控制,动机只有一个——让 ArkTS、Kotlin、Dart 三份实现对同一模型产出逐字节相同的 JSON。两个坑位:

双精度的 1.0 不能变成 1。 JavaScript 的 String(1.0) 输出 "1",Kotlin 和 Dart 的 Double.toString() / double.toString() 却输出 "1.0"——置信度全 0 的场景里,一个 0.0 写成 0 就破坏了对账。所以有了这个函数:

// ohos/src/main/ets/ocr/OcrJson.ets - formatDouble()
export function formatDouble(value: number): string {
  if (Number.isInteger(value)) {
    return value.toFixed(1);         // 整数值保持尾随 .0
  }
  return value.toString();
}

转义表要和 Kotlin/Dart 的行为完全对齐。 jsonQuote 逐字符扫描,转义 "、\、\n、\r、\t,其余 0x20 以下的控制字符统一写成小写十六进制的 \uXXXX(四位补零)——识别结果里出现什么字符是用户图片说了算的,契约必须对任意输入成立。字段顺序固定为 id, source, language, fullText, averageConfidence, durationMs, blocks,列表分隔符用 ", "——这是对齐 Kotlin joinToString 的默认分隔符。真机集成测试断言"原生 result JSON 与 Dart OcrEngine.result(0, revision: 2).toJson() 均为 348 字符,逐字节一致",就是这份契约的验收标准。

5.5 通道协议与可观测性

FlutterOhosOcrPlugin.dispatch 是通道上的唯一分发点,7 个方法各归各位;识别类方法的返回结构固定为 {json, imageBytes, source, width, height, elapsedMs}——JSON 给模型,字节给预览,尺寸和耗时给 UI。错误码是协议的一部分:

code触发场景
IllegalArgumentException参数缺失、类型错误、空字节、空文件名
OCR_VISION_ERRORImageSource / PixelMap / recognizeText 失败
OCR_PICKER_ERROR系统相册选择失败或读取选中文件失败
NATIVE_CONTEXT_MISSING插件没拿到应用上下文
MissingPluginException(Dart 侧)非鸿蒙宿主或插件未注册

Dart 侧的门面 OhosOcr 有两个值得抄的设计:initialize() 不抛异常——探测失败记 false 并写日志,让"非鸿蒙宿主"成为一个可降级状态而不是崩溃源(纯 Dart 的 OcrEngine 在任何平台都能跑,离线演示和对账不受影响);日志是带历史回放的广播流——logs 缓存最近 80 条,新监听者先收历史再收实时,示例的"契约与诊断"页直接把它渲染成滚动列表。查问题不用连 hilog,页面上就是现场。

还有一个小而妙的工程细节:example/ohos/hvigorconfig.ts 在构建配置阶段检测 .dart_tool/package_config.json,缺失时自动跑一次 flutter pub get——专治"先开 DevEco、后 pub get"导致的 package_config.json does not exist(见坑三)。

6. 我踩过的坑(每一条都有真实报错)

这一节可能是全文最值钱的部分。以下坑全部来自这次开发的真实报错和排查记录。

坑一:command-line-tools 的 hvigor 又背锅。 flutter build hap 调 PATH 里的 hvigorw,本机 6.21.2 直接报 Specification Limit Violation——旧 hvigor 校验版本号要求 'x.y.z(api)' 带括号形式,解析不了示例的 26.0.0。修复:把 DevEco Studio 自带 hvigor(6.26.4)的 bin 和 node 目录放到 PATH 前面。教训和空间音频篇同款:鸿蒙构建链路里,先确认 PATH 里那个 hvigorw 是谁家的。

坑二:SDK component missing。 示例 compatibleSdkVersion 写了 6.1.1(24),本机 DevEco 只装了 HarmonyOS 26.0.0 SDK,构建直接找不到组件。修复:改成 6.1.0(23)(正好匹配手头真机),或先在 SDK Manager 装对应 API。插件本身只依赖 API 12+ 的 Core Vision Kit / ImageKit / MediaLibraryKit 接口,是示例工程的目标版本在挑 SDK,不是插件。

坑三:DevEco 直接构建报 package_config.json does not exists。 绕过 Flutter CLI、直接在 DevEco 里 hvigorw assembleHap 时,它不会替你执行 flutter pub get,接着还连锁报 Failed to resolve OhmUrl——插件 HAR 没在 injectNativeModules 之前登记。修复进了 CHANGELOG 1.0.1:example/ohos/hvigorconfig.ts 在配置阶段检测 .dart_tool/package_config.json 与 .flutter-plugins-dependencies,缺失时自动跑一次 flutter pub get。教训:IDE 直构建和 CLI 构建的前置条件差一层,能在构建配置里自愈的就别指望人的记忆。

坑四:签名报 The bundleName ... does not match。 签名 profile 和 AppScope/app.json5 的 bundleName 双向绑定,profile 的设备列表还必须包含当前设备 UDID。修复:在 DevEco 里重新 Automatically generate signature。仓库刻意不带任何签名材料(signingConfigs: []),换机器必做这一步。

坑五:相册按钮报 OCR_PICKER_ERROR。 排查三条:确认设备是 HarmonyOS 12+(PhotoViewPicker 可用);确认选择器返回的 URI 是插件用 fileIo.open 读的、读之前别在外部释放 fd;确认应用在前台(部分定制系统限制后台窗口拉起选择器)。另外记住授权模型是"安全访问图库"按次授权——如果有人试图帮你"补上" READ_IMAGEVIDEO 权限申请,那是画蛇添足,不是修复。

坑六:识别结果置信度全是 0.0,一度以为识别坏了。 真机跑通那天我盯着满屏的 0.0 怀疑人生,翻文档才确认 Core Vision Kit 就是不暴露逐行置信度。修复不是修复,是诚实:模型照实填 0.0,UI 显示"未提供",README 的 FAQ 里专门写了一条"为什么置信度都是 0.0"。系统不给的数据就别造,假的置信度比没有置信度危害大。

坑七:页面显示"仅鸿蒙",isSupported 为 false。 症状是所有按钮无反应,诊断路径固定四步:确认宿主是鸿蒙版 Flutter 且 ohos/ 存在;flutter pub get 后完整构建一次,检查 GeneratedPluginRegistrant.ets 里有没有 FlutterOhosOcrPlugin;hilog -D 0x0C01 里找 flutter_ohos_ocr attached;若 MissingPluginException 出现在启动瞬间,等 initialize() 完成再读 isSupported。和空间音频篇的坑六是同一类病:"注册不上"比"编译不过"更难查,验证手段要提前备好。

7. 现在怎么用:从 Demo 到依赖

经过上面的整理,这个工程是一个标准 Flutter 插件(包名 flutter_ohos_ocr,版本 1.0.1,MIT),示例应用在 example/。插件未发布 pub.dev,用 path 或 git 依赖:

dependencies:
  flutter_ohos_ocr:
    path: ../flutter_ohos_ocr
    # 或者:
    # flutter_ohos_ocr:
    #   git:
    #     url: https://atomgit.com/lqjmac/flutter_ohos_ocr.git
    #     ref: main

接入三步:flutter pub get;确认宿主是鸿蒙版 Flutter(有 ohos/ 目录);需要 rawfile 示例时把图片放进 ohos/entry/src/main/resources/rawfile/(示例的 ocr_sample.png 可直接复用)。构建真机包:

cd example
flutter pub get
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap

更完整的 API 表、通道协议和排错说明在仓库的两份文档里:

  • README.OpenHarmony_CN.md:中文,含能力边界、通道协议、签名指引和 8 条 FAQ;
  • README.OpenHarmony.md:英文,内容与中文版一一对应;
  • example/:三页示例(识别实验室 / 确定性引擎 / 契约与诊断);
  • test/ + example/integration_test/:35 项 Dart 单测、3 项 widget 测试、3 项真机集成测试。

当前版本在 Flutter 3.41.9+ohos-1.0.2、DevEco Studio 6.1.0(HarmonyOS SDK 26.0.0)环境下验证:Dart 单测与 widget 测试全绿,真机集成测试(HUAWEI HAD-W32,HarmonyOS 6.1.0.115 / API 23)通过——插件可用、三个场景的原生 JSON 与 Dart 实现均为 348 字符逐字节一致、自检六项全过、内置小票识别出"门店收银小票"“合计”“66.00”,原生耗时约 140~240 ms;系统相册以"安全访问图库"正常拉起,取消返回无异常。图片不出设备——这是这条链路从设计到验证一直守住的边界。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐