@capacitor/camera 插件在鸿蒙PC平台的适配实践

使用 hionic CLI 在 React + Capacitor 项目中集成相机插件,以及踩坑实录


背景

在之前的文章中,我们完成了从零到一将 React 应用部署到鸿蒙 PC 平台。本文聚焦于 Capacitor 插件 的使用 —— 以 @capacitor/camera 为例,演示如何安装、配置和调试插件,并分享在适配过程中遇到的关键问题与解决方案。


1. 项目结构回顾

MyApp/
├── src/                      # React 前端源码
├── dist/                     # Vite 构建产物
├── openharmony/              # OpenHarmony 原生工程
│   ├── entry/                # 应用主模块
│   │   └── src/main/
│   │       └── resources/
│   │           └── rawfile/
│   │               └── www/  # Web 资源(由 dist/ + 桥接文件组成)
│   └── capacitor/            # Capacitor 适配层(C++ + ArkTS)
├── capacitor.config.json     # Capacitor 配置
└── package.json

2. 安装 @capacitor/camera 插件

hionic CLI 封装了插件安装的完整流程:

hionic plugin add @capacitor/camera

该命令会:

  1. 安装 npm 包 @capacitor/camera
  2. 自动检测并安装对应的 OpenHarmony 适配包 @capacitor-ohos/camera
  3. 注册插件到 CMakeLists.txt(C++ 层)和 capacitor.plugins.json

安装后的目录变化:

openharmony/capacitor/src/main/cpp/
├── Camera/                    # 新增相机插件 C++ 适配层
├── getcapacitor/              # 核心桥接
└── HotCodePushPlugin/         # 热更新

3. 配置相机权限

3.1 module.json5

编辑 openharmony/entry/src/main/module.json5,在 requestPermissions 中添加相机权限声明:

{
  "module": {
    ...
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_permission",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ],
  }
}

注意user_grant 类型的权限(如相机、定位)必须同时声明 reasonusedScene,否则编译会报错。

3.2 string.json

openharmony/entry/src/main/resources/base/element/string.json 添加权限说明文本:

{
  "name": "camera_permission",
  "value": "Allow access to camera for taking photos"
}

4. 前端代码示例

4.1 动态导入策略

src/App.jsx 中,我们使用 动态 import() 而非顶层 import

const takePhoto = async () => {
  try {
    setError(null)
    const { Camera } = await import('@capacitor/camera')
    const image = await Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: 'DataUrl',
    })
    setPhoto(image.dataUrl)
  } catch (err) {
    setError(`Camera error: ${err.message}`)
  }
}

这样做的好处:在 npm run dev 浏览器开发模式下,页面不会因找不到 Capacitor 运行时而直接崩溃;只有点击按钮时才会尝试加载插件,在浏览器中优雅降级为错误提示。

4.2 UI 组件

<section id="camera-demo">
  <h2>📸 Camera Demo</h2>
  <p>Test the <code>@capacitor/camera</code> plugin on your device</p>
  <button onClick={takePhoto}>Take Photo</button>
  {error && <p className="camera-error">{error}</p>}
  {photo && (
    <div className="photo-preview">
      <img src={photo} alt="Captured" />
    </div>
  )}
</section>

5. 关键踩坑:Web 资源同步

5.1 问题现象

在完成前端开发和插件安装后,部署到设备发现 相机按钮没有出现。打开的是旧页面。

5.2 根因分析

hionic start capacitor 生成的项目存在一个配置断层

capacitor.config.json  →  webDir: "www"
Vite 构建输出          →  dist/
openharmony rawfile    →  rawfile/www/

三者之间的指向关系不一致:

环节 实际目录 问题
Vite 默认输出 dist/ 与 Capacitor 配置不匹配
capacitor.config.json webDir: "www" www/ 目录不存在
OHOS rawfile rawfile/www/ 存的是旧模板文件

同时,React 构建产物中缺少 Capacitor 桥接文件cordova.jsnative-bridge.js),导致原生插件(相机等)无法与 Web 端通信。

5.3 解决方案

第一步:修正 webDir

// capacitor.config.json
{
  "appId": "com.nutpi.MyApp",
  "appName": "MyHarmonyApp",
  "webDir": "dist"
}

第二步:补充桥接文件

index.html 中添加桥接脚本引用:

<body>
  <div id="root"></div>
  <script src="./cordova.js"></script>
  <script src="./native-bridge.js"></script>
  <script type="module" src="/src/main.jsx"></script>
</body>

第三步:构建 + 同步脚本

完整的发布流程:

# 1. 构建前端
npx vite build

# 2. 复制桥接文件到构建目录(从 git 历史或 node_modules 恢复)
cp cordova.js native-bridge.js cordova_plugins.js dist/

# 3. 同步到 OHOS rawfile
rm -rf openharmony/entry/src/main/resources/rawfile/www
cp -r dist openharmony/entry/src/main/resources/rawfile/www

# 4. 编译原生 HAP
cd openharmony
hvigorw assembleHap

# 5. 部署到设备
hionic run openharmony

6. 安装 @ionic-native/in-app-browser 插件与应用内浏览器

第二个插件示例:应用内浏览器,让用户在不离开 App 的情况下浏览网页。

6.1 安装

hionic plugin add @ionic-native/in-app-browser

该命令会:

  1. 安装 npm 包 @ionic-native/in-app-browser
  2. 安装 OHOS 原生适配包 @ionic-native-ohos/in-app-browser
  3. config.xml 中注册 InAppBrowser feature
  4. cordova_plugins.js 中注册 JS 模块
  5. rawfile/www/plugins/ 下生成 inappbrowser.js

6.2 配置

编辑 openharmony/entry/src/main/resources/rawfile/config.xml,添加浏览器偏好配置:

<preference name="InAppBrowserLocation" value="yes" />
<preference name="InAppBrowserHidden" value="no" />
<preference name="InAppBrowserFullscreen" value="yes" />
<preference name="InAppBrowserCloseButtonColor" value="#aa3bff" />
<preference name="InAppBrowserToolbar" value="yes" />
<preference name="InAppBrowserToolbarPosition" value="top" />
<preference name="InAppBrowserHideNavigationButtons" value="no" />
<preference name="InAppBrowserClearCache" value="no" />
<preference name="InAppBrowserClearSessionCache" value="no" />

6.3 前端代码

InAppBrowser 是 Cordova 风格插件,通过全局 window.InAppBrowser 对象调用:

const openBrowser = async () => {
  try {
    // 从 window 上获取(Cordova 桥接注入)
    // 如果不可用,降级尝试 npm 包
    const InAppBrowser = window.InAppBrowser
      || (await import('@ionic-native/in-app-browser')).InAppBrowser

    // 参数:url, target, options(逗号分隔的字符串)
    const browser = InAppBrowser.create(
      'https://capacitorjs.com',
      '_blank',
      'location=yes,toolbar=yes,hidden=no'
    )

    browser.addEventListener('loadstop', () => {
      console.log('Page loaded')
      browser.show()
    })
  } catch (err) {
    console.error('InAppBrowser error:', err)
  }
}

6.4 与 Capacitor 插件对比

对比项 @capacitor/camera @ionic-native/in-app-browser
类型 Capacitor 插件 Cordova 插件
调用方式 await import('@capacitor/camera') window.InAppBrowser (全局注入)
桥接机制 Capacitor 桥接 Cordova exec 桥接
依赖文件 无额外依赖 依赖 cordova.js + native-bridge.js
选项格式 JavaScript 对象 逗号分隔字符串
浏览器兼容 import() 失败时 catch 降级 window.InAppBrowser 为 undefined 时降级

7. 完整开发工作流

graph TD
    A[安装插件] --> B[配置权限/偏好]
    B --> C[编写前端代码]
    C --> D[vite build]
    D --> E[复制桥接文件到 dist/]
    E --> F[同步到 rawfile/www]
    F --> G[hvigor assembleHap]
    G --> H[hionic run openharmony]

    A --> A1[Capacitor 插件<br/>hionic plugin add @capacitor/xxx]
    A --> A2[Cordova 插件<br/>hionic plugin add @ionic-native/xxx]

8. 验证结果

编译输出:

> hvigor BUILD SUCCESSFUL in 10 s 778 ms

HAP 产物:

openharmony/entry/build/default/outputs/default/
├── entry-default-signed.hap       (26.3 MB)
└── entry-default-unsigned.hap     (26.3 MB)

部署后设备上显示:

  • 📸 Camera Demo 区域
  • Take Photo 按钮
  • 点击后弹出相机权限请求
  • 拍照后照片回显在页面中


9. 技术要点总结

要点 说明
动态 import 使用 await import('@capacitor/camera') 而不是静态 import,兼容浏览器开发模式
webDir 一致性 capacitor.config.jsonwebDir 必须与前端构建输出目录一致
桥接文件 cordova.js + native-bridge.js 必须跟随 Web 资源一起部署到 rawfile
权限声明 user_grant 权限需要同时声明 reasonusedScene
构建顺序 前端构建 → 同步资源 → 编译原生 → 部署,不能跳过中间步骤

10. 项目开源地址

本文对应的完整项目代码已开源:

🔗 https://atomgit.com/jianguoxu/hionic

包含已集成的 @capacitor/camera 插件、完整的 React 前端示例、OpenSSL 预编译库,以及从零到一的部署教程。


参考资源