本文是《React Native x HarmonyOS NEXT 创新能力接入方案:从 RNOH 到原生能力桥接》系列第 4 篇。上一篇我们完成了 TTS 语音合成,本篇继续把 HarmonyOS 的视觉识别能力接入 React Native 项目,实现“选择图片 → OCR 识别 → 结果回显到 RN 页面”的完整链路。
源码: https://atomgit.com/huqi/RNHarmonySkuAssistant


1. 本篇要实现什么?

本篇我们要做一个 React Native 调用 HarmonyOS OCR 文字识别能力 的接入方案 Demo。

业务场景不照搬 BabyOne 的“疫苗本 / 体检报告”,而是换成更适合工具类 App 和跨境电商场景的:

  • 商品包装文字识别;
  • 物流面单文字识别;
  • 商品标签识别;
  • 说明书文字提取;
  • 发票 / 表单辅助录入。

最终效果:

React Native 页面
    ↓
选择本地图片
    ↓
调用 RNOH TurboModule
    ↓
ArkTS 原生模块调用 HarmonyOS OCR 能力
    ↓
识别结果返回 React Native
    ↓
页面展示识别文本

最终页面建议设计成:

RN Harmony SKU Assistant
OCR 商品包装识别

[选择图片]

图片预览区域

[开始识别]

识别结果:
Product Name: Stainless Steel Storage Rack
Material: 304 Stainless Steel
Size: 30 x 12 x 8 cm
Made in China

真机效果如下:

OCR 功能入口页

OCR 页面初始状态

系统图片选择页面

OCR 识别结果回显

这次真机识别的图片是一张设备屏幕照片。Core Vision Kit 最终识别出 9 个文本块,并将结果回显到 RN 页面,日志显示耗时约 628ms。


2. 为什么 OCR 适合作为第一个视觉能力?

在 React Native 适配 HarmonyOS 的过程中,OCR 是非常适合做教程的能力,原因有三个:

第一,OCR 结果直观。用户选择一张图片后,页面能直接看到文字结果,截图很有说服力。

第二,OCR 业务场景广。无论是母婴、教育、办公、电商、物流,都会遇到“图片文字转结构化文本”的需求。

第三,OCR 可以很好地体现跨端框架接入原生能力的价值。React Native 负责业务界面和交互,HarmonyOS 原生侧负责调用系统 OCR 能力,二者通过 RNOH TurboModule 打通。

HarmonyOS / 华为 HMS 生态中,OCR 通常属于视觉识别能力范畴。公开文档中,华为文字识别能力支持将图像中的文字转换为可编辑文本,并提供 SDK 集成方式;这类能力非常适合封装成跨端可调用模块。


3. 本篇技术架构

本篇的完整调用链路如下:

OCRPage.tsx
    ↓
NativeOCRModule.recognizeText(imageUri)
    ↓
RNOH TurboModule
    ↓
OCRModule.ets
    ↓
HarmonyOS OCR / Vision Kit 能力
    ↓
Promise<OCRResult>
    ↓
React Native 页面渲染识别结果

可以拆成三层:

React Native 业务层
├── 图片选择
├── 图片预览
├── 识别按钮
├── Loading / Error / Empty 状态
└── 识别结果展示

RNOH 桥接层
├── NativeOCRModule.ts
├── TurboModuleRegistry
├── Codegen
└── Promise 结果回调

HarmonyOS 原生层
├── OCRModule.ets
├── 图片 URI 处理
├── OCR 能力调用
├── 权限处理
└── 结果格式化

4. 项目目录建议

本篇建议在上一节项目基础上继续扩展目录:

RNHarmonySkuAssistant
├── App.tsx
├── src
│   ├── pages
│   │   └── OCRPage.tsx
│   ├── native
│   │   └── NativeOCRModule.ts
│   └── types
│       └── ocr.ts
│
└── harmony
    └── entry
        └── src
            └── main
                └── ets
                    ├── modules
                    │   └── OCRModule.ets
                    └── RNPackagesFactory.ets

说明:

  • OCRPage.tsx:React Native 侧页面;
  • NativeOCRModule.ts:JS / TS 侧原生模块声明;
  • ocr.ts:OCR 结果类型定义;
  • OCRModule.ets:ArkTS 原生能力封装;
  • RNPackagesFactory.ets:RNOH 原生模块注册。

5. 定义 OCR 返回数据结构

先在 RN 侧定义 OCR 结果类型。

新建:

src/types/ocr.ts

示例代码:

export interface OCRTextBlock {
  text: string;
  confidence?: number;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
}

export interface OCRResult {
  fullText: string;
  blocks: OCRTextBlock[];
  imageUri: string;
  costTime?: number;
}

这里不要只返回一个字符串,建议保留 blocks

原因是后面可以继续扩展:

  • 按行展示识别结果;
  • 高亮图片中的识别区域;
  • 过滤低置信度文本;
  • 对商品名称、规格、产地、条码做结构化提取。

6. React Native 侧声明 NativeOCRModule

新建:

src/native/NativeOCRModule.ts

示例代码:

import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface OCRTextBlock {
  text: string;
  confidence?: number;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
}

export interface OCRResult {
  fullText: string;
  blocks: OCRTextBlock[];
  imageUri: string;
  costTime?: number;
}

export interface Spec extends TurboModule {
  recognizeText(imageUri: string): Promise<OCRResult>;
}

export default TurboModuleRegistry.get<Spec>('OCRModule') as Spec | null;

注意:

TurboModuleRegistry.get<Spec>('OCRModule')

这里的 'OCRModule' 要和 ArkTS 原生模块注册名称保持一致。

如果名称不一致,RN 页面调用时会出现模块为 null 或找不到方法的问题。


7. React Native 页面实现

新建:

src/pages/OCRPage.tsx

示例代码如下:

import React, { useState } from 'react';
import {
  ActivityIndicator,
  Alert,
  Image,
  Pressable,
  SafeAreaView,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import NativeOCRModule from '../native/NativeOCRModule';
import type { OCRResult } from '../types/ocr';

export default function OCRPage() {
  const [imageUri, setImageUri] = useState<string>('');
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<OCRResult | null>(null);

  const handleChooseImage = async () => {
    // 这里先用 mock 图片路径占位。
    // 实际项目中建议在 HarmonyOS 原生侧封装图片选择能力,
    // 或使用已经适配 OpenHarmony 的图片选择库。
    const mockUri = 'file://demo/product_package.jpg';
    setImageUri(mockUri);
    setResult(null);
  };

  const handleRecognize = async () => {
    if (!imageUri) {
      Alert.alert('提示', '请先选择一张图片');
      return;
    }

    if (!NativeOCRModule) {
      Alert.alert('错误', 'OCRModule 未注册,请检查 RNOH 原生模块配置');
      return;
    }

    try {
      setLoading(true);
      const data = await NativeOCRModule.recognizeText(imageUri);
      setResult(data);
    } catch (error) {
      console.error('[OCR] recognize failed:', error);
      Alert.alert('识别失败', String(error));
    } finally {
      setLoading(false);
    }
  };

  return (
    <SafeAreaView style={styles.safeArea}>
      <ScrollView contentContainerStyle={styles.container}>
        <Text style={styles.title}>OCR 商品包装识别</Text>
        <Text style={styles.subtitle}>
          React Native 调用 HarmonyOS OCR 能力,识别商品包装、标签、物流面单中的文字。
        </Text>

        <View style={styles.card}>
          <Text style={styles.cardTitle}>图片</Text>

          {imageUri ? (
            <View style={styles.imageBox}>
              <Text style={styles.imagePlaceholder}>已选择图片:{imageUri}</Text>
              {/* 真机项目中可以用 Image 展示真实 URI */}
              {/* <Image source={{ uri: imageUri }} style={styles.image} /> */}
            </View>
          ) : (
            <View style={styles.emptyImageBox}>
              <Text style={styles.emptyText}>暂未选择图片</Text>
            </View>
          )}

          <Pressable style={styles.secondaryButton} onPress={handleChooseImage}>
            <Text style={styles.secondaryButtonText}>选择图片</Text>
          </Pressable>

          <Pressable style={styles.primaryButton} onPress={handleRecognize}>
            <Text style={styles.primaryButtonText}>开始 OCR 识别</Text>
          </Pressable>
        </View>

        <View style={styles.card}>
          <Text style={styles.cardTitle}>识别结果</Text>

          {loading && (
            <View style={styles.loadingBox}>
              <ActivityIndicator />
              <Text style={styles.loadingText}>正在识别中...</Text>
            </View>
          )}

          {!loading && !result && (
            <Text style={styles.emptyText}>识别结果会显示在这里</Text>
          )}

          {!loading && result && (
            <View>
              <Text style={styles.fullText}>{result.fullText}</Text>

              <Text style={styles.metaText}>
                共识别 {result.blocks.length} 个文本块
                {result.costTime ? `,耗时 ${result.costTime}ms` : ''}
              </Text>

              {result.blocks.map((block, index) => (
                <View key={`${block.text}-${index}`} style={styles.blockItem}>
                  <Text style={styles.blockIndex}>#{index + 1}</Text>
                  <Text style={styles.blockText}>{block.text}</Text>
                  {typeof block.confidence === 'number' && (
                    <Text style={styles.confidenceText}>
                      置信度:{Math.round(block.confidence * 100)}%
                    </Text>
                  )}
                </View>
              ))}
            </View>
          )}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#F7F8FA',
  },
  container: {
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    color: '#111827',
  },
  subtitle: {
    marginTop: 8,
    fontSize: 14,
    color: '#6B7280',
    lineHeight: 22,
  },
  card: {
    marginTop: 20,
    backgroundColor: '#FFFFFF',
    borderRadius: 18,
    padding: 18,
  },
  cardTitle: {
    fontSize: 17,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 14,
  },
  imageBox: {
    minHeight: 160,
    borderRadius: 14,
    backgroundColor: '#F3F4F6',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 16,
  },
  emptyImageBox: {
    height: 160,
    borderRadius: 14,
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: '#D1D5DB',
    alignItems: 'center',
    justifyContent: 'center',
  },
  image: {
    width: '100%',
    height: 160,
    borderRadius: 14,
  },
  imagePlaceholder: {
    fontSize: 13,
    color: '#4B5563',
    textAlign: 'center',
  },
  emptyText: {
    fontSize: 14,
    color: '#9CA3AF',
  },
  secondaryButton: {
    marginTop: 16,
    borderRadius: 14,
    paddingVertical: 13,
    alignItems: 'center',
    backgroundColor: '#EEF2FF',
  },
  secondaryButtonText: {
    fontSize: 15,
    color: '#1D4ED8',
    fontWeight: '600',
  },
  primaryButton: {
    marginTop: 12,
    borderRadius: 14,
    paddingVertical: 13,
    alignItems: 'center',
    backgroundColor: '#0A59F7',
  },
  primaryButtonText: {
    fontSize: 15,
    color: '#FFFFFF',
    fontWeight: '600',
  },
  loadingBox: {
    paddingVertical: 20,
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    color: '#6B7280',
  },
  fullText: {
    fontSize: 15,
    lineHeight: 24,
    color: '#111827',
  },
  metaText: {
    marginTop: 12,
    color: '#6B7280',
    fontSize: 13,
  },
  blockItem: {
    marginTop: 12,
    padding: 12,
    borderRadius: 12,
    backgroundColor: '#F9FAFB',
  },
  blockIndex: {
    fontSize: 12,
    color: '#6B7280',
  },
  blockText: {
    marginTop: 4,
    fontSize: 14,
    color: '#111827',
    lineHeight: 21,
  },
  confidenceText: {
    marginTop: 4,
    fontSize: 12,
    color: '#6B7280',
  },
});

8. 先用 Mock 原生模块跑通页面

真实接入 OCR 前,建议先用 Mock 数据跑通 RN 页面和 TurboModule 调用链路。

这样做的好处是:

  • 先确认 RN 页面逻辑没问题;
  • 先确认 TurboModule 注册没问题;
  • 后面再替换成真实 OCR API,排查范围更小。

ArkTS 原生模块可以先返回模拟数据。

示例:

// OCRModule.ets
// 注意:以下为教学示例,具体基类、导入路径、注册方式请以当前 RNOH 工程模板为准。

export interface OCRTextBlock {
  text: string
  confidence?: number
  left?: number
  top?: number
  width?: number
  height?: number
}

export interface OCRResult {
  fullText: string
  blocks: OCRTextBlock[]
  imageUri: string
  costTime?: number
}

export class OCRModule {
  async recognizeText(imageUri: string): Promise<OCRResult> {
    const start = Date.now()

    // 第一步先返回 Mock 数据,验证 RN ↔ ArkTS 调用链路
    const blocks: OCRTextBlock[] = [
      {
        text: 'Stainless Steel Storage Rack',
        confidence: 0.98,
      },
      {
        text: 'Material: 304 Stainless Steel',
        confidence: 0.96,
      },
      {
        text: 'Size: 30 x 12 x 8 cm',
        confidence: 0.95,
      },
      {
        text: 'Made in China',
        confidence: 0.93,
      },
    ]

    return {
      fullText: blocks.map((item) => item.text).join('\n'),
      blocks,
      imageUri,
      costTime: Date.now() - start,
    }
  }
}

运行后如果 RN 页面能展示 Mock 识别结果,说明最小链路已经打通。


9. 接入真实 HarmonyOS OCR 能力

Mock 跑通之后,再把 recognizeText 替换成真实 OCR 调用。本文实测版本使用 @kit.CoreVisionKit 中的 textRecognition.recognizeText

真实调用链路如下:

1. 校验 imageUri 是否为空
2. 使用 fileIo.open(imageUri) 打开系统图片选择器返回的 file://media URI
3. 使用 image.createImageSource(file.fd) 创建 ImageSource
4. 使用 imageSource.createPixelMap() 解码图片
5. 调用 textRecognition.init() 初始化 OCR 服务
6. 调用 textRecognition.recognizeText({ pixelMap }, config)
7. 将 TextRecognitionResult.blocks / lines 转换成 OCRResult.blocks
8. 释放 OCR 服务、PixelMap、ImageSource 和文件句柄

核心代码如下:

import { fileIo, picker } from '@kit.CoreFileKit';
import { image } from '@kit.ImageKit';
import { textRecognition } from '@kit.CoreVisionKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { UITurboModule, UITurboModuleContext } from '@rnoh/react-native-openharmony';

interface OCRTextBlock {
  text: string;
  confidence?: number;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
}

interface OCRResult {
  fullText: string;
  blocks: OCRTextBlock[];
  imageUri: string;
  costTime?: number;
}

export class OCRModule extends UITurboModule {
  static readonly NAME = 'OCRModule';

  constructor(ctx: UITurboModuleContext) {
    super(ctx);
  }

  async pickImage(): Promise<string> {
    const photoSelectOptions = new picker.PhotoSelectOptions();
    photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    photoSelectOptions.maxSelectNumber = 1;

    const photoViewPicker = new picker.PhotoViewPicker();
    const photoSelectResult = await photoViewPicker.select(photoSelectOptions);

    if (photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
      return photoSelectResult.photoUris[0];
    }

    return Promise.reject('CANCEL');
  }

  async recognizeText(imageUri: string): Promise<OCRResult> {
    if (!imageUri) {
      return Promise.reject('IMAGE_URI_EMPTY');
    }

    const start = Date.now();
    let source: image.ImageSource | null = null;
    let pixelMap: image.PixelMap | null = null;
    let file: fileIo.File | null = null;
    let serviceInitialized = false;

    try {
      file = await fileIo.open(imageUri, fileIo.OpenMode.READ_ONLY);
      source = image.createImageSource(file.fd);
      pixelMap = await source.createPixelMap();

      serviceInitialized = await textRecognition.init();
      if (!serviceInitialized) {
        return Promise.reject('OCR_SERVICE_INIT_FAILED');
      }

      const nativeResult = await textRecognition.recognizeText(
        { pixelMap },
        { isDirectionDetectionSupported: true },
      );

      const blocks: OCRTextBlock[] = nativeResult.blocks.flatMap((block) =>
        block.lines.map((line) => ({
          text: line.value,
        })),
      );

      return {
        fullText: nativeResult.value || blocks.map((item) => item.text).join('\n'),
        blocks,
        imageUri,
        costTime: Date.now() - start,
      };
    } catch (err) {
      const error = err as BusinessError;
      return Promise.reject(`OCR 识别失败: ${error.message || String(err)}`);
    } finally {
      if (serviceInitialized) {
        await textRecognition.release().catch(() => {});
      }
      if (pixelMap) {
        await pixelMap.release().catch(() => {});
      }
      if (source) {
        await source.release().catch(() => {});
      }
      if (file) {
        await fileIo.close(file).catch(() => {});
      }
    }
  }
}

当前项目还把 line.cornerPoints 转换成了 left / top / width / height,方便后续做图片区域高亮。由于 Core Vision Kit 当前返回结构里没有置信度字段,RN 类型中的 confidence 保持为可选字段。

真机调用日志如下:

[OCRModule] pickImage success: file://media/Photo/1619/IMG_1781788459_1608/IMG_20260618_211239.jpg
[OCRModule] recognizeText success, blocks: 9, cost: 628ms

构建和安装命令如下:

npm run harmony
cd harmony
env -u OHOS_SDK -u OHOS_SDK_PATH \
  DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk/default \
  PATH="/Applications/DevEco-Studio.app/Contents/tools/node/bin:/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin:/Applications/DevEco-Studio.app/Contents/tools/ohpm/bin:/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains:$PATH" \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHap --mode module -p module=entry --no-daemon

hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b host.huqi.sku_assistant

需要注意:如果命令行环境里同时存在旧版 OHOS_SDK / OHOS_SDK_PATH,可能会出现 SDK component missing。本文实测时通过显式指定 DevEco Studio 自带 SDK 和构建工具路径解决。

完整实现时还需要关注:

1. 权限与隐私提示
2. 大图解码内存占用
3. OCR 服务初始化失败
4. 将原生识别结果转换成 OCRResult
5. PixelMap / ImageSource / fd 资源释放
6. 设备不支持 OCR 能力时的降级提示

10. 图片选择能力怎么处理?

OCR 要处理图片,所以图片选择也是一个必须解决的问题。

有两种方案:

方案一:RN 侧使用已适配 OpenHarmony 的图片选择库

优点:

  • RN 页面写法更接近 Android / iOS;
  • 图片选择逻辑保持跨端统一;
  • 后续维护成本较低。

缺点:

  • 依赖第三方库是否已经适配 RNOH;
  • 如果库未适配,需要额外处理 OpenHarmony 端实现。

方案二:自己封装 HarmonyOS 原生图片选择模块

优点:

  • 可控性强;
  • 适配 HarmonyOS 权限和 URI 处理更直接;
  • 可以和 OCR 模块合并成一个“选择并识别”能力。

缺点:

  • 需要额外写 ArkTS 原生代码;
  • 需要处理权限、文件 URI、图片解码等细节。

本系列建议先用方案二,因为它更符合“RNOH 调 HarmonyOS 原生能力”的教程定位。

可以把接口设计成两个方法:

export interface Spec extends TurboModule {
  pickImage(): Promise<string>;
  recognizeText(imageUri: string): Promise<OCRResult>;
}

页面调用逻辑改成:

const handleChooseImage = async () => {
  if (!NativeOCRModule) {
    Alert.alert('错误', 'OCRModule 未注册');
    return;
  }

  const uri = await NativeOCRModule.pickImage();
  setImageUri(uri);
  setResult(null);
};

这样体验会更完整。


11. 权限配置建议

OCR 场景可能涉及:

  • 读取相册图片;
  • 访问文件;
  • 调用相机拍照;
  • 使用系统视觉识别能力。

建议在 HarmonyOS 工程中明确检查:

1. module.json5 中是否声明所需权限
2. 是否需要运行时申请权限
3. 用户拒绝权限时是否有兜底提示
4. 图片 URI 是否有访问权限
5. OCR Kit 是否需要在 AGC / 控制台开通相关服务

RN 页面不要直接假设一定有权限,而是要处理错误状态:

try {
  const data = await NativeOCRModule.recognizeText(imageUri);
  setResult(data);
} catch (error) {
  if (String(error).includes('PERMISSION')) {
    Alert.alert('权限不足', '请允许应用访问图片后再试');
  } else {
    Alert.alert('识别失败', String(error));
  }
}

12. 错误码设计

建议不要直接把原生异常丢给 RN 页面,而是设计统一错误码。

IMAGE_URI_EMPTY          图片路径为空
IMAGE_READ_FAILED        图片读取失败
IMAGE_FORMAT_UNSUPPORTED 图片格式不支持
OCR_SERVICE_UNAVAILABLE  OCR 能力不可用
OCR_RECOGNIZE_FAILED     OCR 识别失败
PERMISSION_DENIED        权限被拒绝
MODULE_NOT_REGISTERED    原生模块未注册

ArkTS 返回错误时可以带上:

{
  code: 'OCR_RECOGNIZE_FAILED',
  message: '文字识别失败,请更换清晰图片后重试'
}

RN 页面根据错误码做友好提示。


13. OCR 结果展示优化

最简单的展示方式是直接展示 fullText

但如果想让文章更像真实项目,可以继续做三类优化。

1)按文本块展示

适合调试 OCR 结果:

#1 Product Name: Stainless Steel Storage Rack
#2 Material: 304 Stainless Steel
#3 Size: 30 x 12 x 8 cm
#4 Made in China

2)按字段提取

适合跨境电商场景:

商品名称:Stainless Steel Storage Rack
材质:304 Stainless Steel
尺寸:30 x 12 x 8 cm
产地:Made in China

3)低置信度提醒

如果置信度低于 80%,可以提示用户重新拍摄:

const lowConfidenceBlocks = result.blocks.filter(
  (item) => typeof item.confidence === 'number' && item.confidence < 0.8,
);

页面提示:

部分文字识别置信度较低,建议重新拍摄更清晰图片。

14. 常见问题

问题 1:NativeOCRModule 为 null

检查:

1. TurboModule 名称是否一致
2. ArkTS 模块是否注册
3. Codegen 是否重新执行
4. HarmonyOS 工程是否重新编译
5. RN 侧是否引入了正确的 NativeOCRModule 文件

尤其注意:

TurboModuleRegistry.get<Spec>('OCRModule')

必须和原生侧注册名保持一致。


问题 2:图片路径传到原生侧后无法读取

常见原因:

1. URI 不是原生侧可访问路径
2. 没有图片读取权限
3. 图片选择库返回的是临时 URI
4. 原生侧没有做 URI 到文件 / PixelMap 的转换
5. 图片格式不支持

建议先在 ArkTS 侧打印 imageUri,确认传入路径是否符合预期。


问题 3:OCR 识别结果为空

可能原因:

1. 图片太模糊
2. 文字太小
3. 光线不足
4. 图片旋转方向异常
5. OCR 能力没有正确初始化
6. 当前语言不在识别支持范围内

建议准备 3 类测试图片:

1. 清晰英文商品包装图
2. 清晰中文标签图
3. 物流面单 / 表单类图片

不要一开始就用复杂背景图片。


问题 4:iOS / Android 可以用的 OCR 库,在 HarmonyOS 上不可用

这是 RNOH 适配中非常常见的问题。

原因是很多 RN 三方库只实现了 Android / iOS 原生端,没有 OpenHarmony 实现。

解决方案:

1. 查看该库是否已有 OpenHarmony 适配版本
2. 如果没有,自己写 ArkTS 原生模块
3. 用 TurboModule 对外暴露统一 JS API
4. RN 页面保持不变,只替换平台实现

这也是本系列要强调的核心思路:

不是等所有三方库都适配 HarmonyOS,而是把关键原生能力封装成可控的 RNOH 模块。


15. 本篇小结

本篇完成了 React Native 调用 HarmonyOS OCR 能力的完整设计:

RN 页面
    ↓
NativeOCRModule
    ↓
RNOH TurboModule
    ↓
ArkTS OCRModule
    ↓
HarmonyOS OCR 能力
    ↓
识别结果回显到 RN 页面

这一篇的重点不是单纯“识别文字”,而是建立一个可复用的视觉能力接入范式:

图片输入
    ↓
原生视觉能力处理
    ↓
结构化结果返回
    ↓
React Native 业务页面消费

后续很多能力都可以沿用这个模式:

  • 端侧 AI 主体分割;
  • 商品图抠图;
  • 图片质量检测;
  • 条码识别;
  • 表单识别;
  • 商品信息结构化。

下一篇我们继续写更有视觉冲击力的一篇:

React Native x HarmonyOS 端侧 AI:商品图主体分割与背景替换

下一篇会实现:

上传商品图
    ↓
端侧 AI 主体分割
    ↓
生成主体 Mask
    ↓
选择背景色
    ↓
生成白底图 / 彩色背景图
Logo

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

更多推荐