Flutter 鸿蒙端侧 OCR 实战:让文字识别留在设备上
识别链路怎么接、跨端 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 Studio | 6.1.0(HarmonyOS SDK 26.0.0 / API 26) |
| Flutter(鸿蒙版) | 3.41.9+ohos-1.0.2,Dart 3.11.0+ |
| hvigor | 6.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_ERROR | ImageSource / 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;系统相册以"安全访问图库"正常拉起,取消返回无异常。图片不出设备——这是这条链路从设计到验证一直守住的边界。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)