Capacitor 插件鸿蒙化实战:@capacitor/device 从安装到模拟器验证全记录
本文聚焦插件层:以 CPF-Ionic 鸿蒙化的
@capacitor/device插件为例,完整走通"安装插件 → React 调用 → 构建签名 → 模拟器运行验证"全链路,所有步骤实测。
一、插件生态背景
1.1 CPF-Ionic 插件体系
Capacitor 的设备能力全部通过插件(@capacitor/xxx)提供。华为 CPF-Ionic 团队按"原插件包名不变、原生实现替换为 OHOS"的原则完成了 29 个官方插件 + 14 个 Ionic 三方插件的鸿蒙化,完整清单见 ionic-readme(已同步至 AtomGit,组织主页 https://atomgit.com/cpf-ionic ):
- Capacitor 官方插件:app、browser、camera、device、filesystem、keyboard、barcode-scanner、app-launcher、clipboard、geolocation、haptics、push-notifications、network、share、status-bar、text-zoom、action-sheet、dialog、screen-reader、splash-screen、toast、file-transfer、file-viewer、inappbrowser、local-notifications、motion、preferences、privacy-screen、screen-orientation
- Ionic 三方插件(
@ionic-native/xxx对应):status-bar、splash-screen、file、in-app-browser、device、file-transfer、app-version、camera、clipboard、file-opener、keyboard、network、android-permissions、pdf-generator
1.2 鸿蒙化插件的双包结构
每个鸿蒙化插件由两个 npm 包组成:
| 包 | 角色 | 安装位置 |
|---|---|---|
@capacitor/device(官方原包) | Web/API 层:TS 类型定义 + Promise API,Web 层调用的入口 | 前端工程 node_modules |
@capacitor-ohos/device | OHOS 原生实现:ArkTS + C++(Device.ets / Device.cpp / Device.h) | 由 hionic 拷入openharmony/capacitor/参与编译 |
Web 层调用Device.getInfo()时代码与 Android/iOS完全一致——插件鸿蒙化的全部意义就在于此:API 契约不变,原生实现替换。
1.3 插件接入鸿蒙工程需要做的四件事
查看插件的plugin.xml可知,一个 OHOS Capacitor 插件接入壳工程需要四处修改:
- 插件注册表:
entry/src/main/resources/rawfile/capacitor.plugins.json加{"pkg": "@capacitor/device", "classpath": "Device"}; - CMake 编译:
capacitor/src/main/cpp/CMakeLists.txt加add_subdirectory(Device)并链入Device库; - 源码拷贝:C++(Device.h/.cpp/CMakeLists.txt)→
cpp/Device/,ArkTS(Device.ets)→ets/components/Device/; - ArkTS 编译范围:
capacitor/build-profile.json5的buildOption.arkOptions.runtimeOnly.sources加入 Device.ets。
好消息:这四步 hionic 全部自动化,一条命令搞定(见下文)。
二、环境
延续上一篇的 capacitorMyApp 项目(hionic 2.1.16 / Capacitor 8.5.2 / @capacitor-ohos/ohos 8.0.2 / openssl 已集成 / 模拟器 127.0.0.1:5555 在线)。
三、逐步操作过程
第 1 步:安装插件
hionic pluginadd@capacitor/device一条命令,hionic 自动完成了上面第一节的全部四件事,关键输出:
log: ✓ Added runtimeOnly source: ./src/main/ets/components/Device/Device.ets log: Installing CMakeLists configuration to: src/main/cpp/CMakeLists.txt log: Updated CMakeLists.txt: src/main/cpp/CMakeLists.txt log: - add_subdirectory(Device) log: Installing config-json to: src/main/resources/rawfile/capacitor.plugins.json log: Successfully added plugin: @capacitor/device验证四处修改都已落盘:
# ① 注册表$catopenharmony/entry/src/main/resources/rawfile/capacitor.plugins.json[{"pkg":"@capacitor/CapacitorPlugin","classpath":"CapacitorPlugin"},{"pkg":"@capacitor/device","classpath":"Device"}← 新增]# ② CMake$grepDevice openharmony/capacitor/src/main/cpp/CMakeLists.txt add_subdirectory(Device)← 第20行 Device ← target_link_libraries 中链入# ③ 源码$lsopenharmony/capacitor/src/main/cpp/Device/ CMakeLists.txt Device.cpp Device.h $lsopenharmony/capacitor/src/main/ets/components/Device/ Device.ets# ④ npm 双包$grepdevice package.json"@capacitor-ohos/device":"^8.0.2"← 鸿蒙原生实现"@capacitor/device":"^8.0.3"← 官方 API 层卸载同样一条命令:
hionic plugin remove @capacitor/device。
第 2 步:React 层调用插件
修改src/App.jsx(与 Android/iOS 上的写法完全一致):
import { useState, useEffect } from 'react' import { Device } from '@capacitor/device' function App() { const [device, setDevice] = useState(null) useEffect(() => { const load = async () => { try { const [info, id, battery, lang] = await Promise.all([ Device.getInfo(), Device.getId(), Device.getBatteryInfo(), Device.getLanguageTag(), ]) setDevice({ info, id, battery, lang }) console.log('Device info:', info) console.log('Device id:', id) } catch (e) { console.error('Device plugin error:', e) } } load() }, []) // ... 渲染部分追加设备信息区块 return ( <> {/* 原有 UI */} {device && ( <div className="device-info"> <h2>Device Info (Capacitor OHOS)</h2> <ul> <li>name: {device.info.name}</li> <li>model: {device.info.model}</li> <li>platform: {device.info.platform}</li> <li>operatingSystem: {device.info.operatingSystem}</li> <li>osVersion: {device.info.osVersion}</li> <li>manufacturer: {device.info.manufacturer}</li> <li>isVirtual: {String(device.info.isVirtual)}</li> <li>memUsed: {device.info.memUsed} bytes</li> <li>identifier (ODID): {device.id.identifier}</li> <li>batteryLevel: {device.battery.batteryLevel ?? 'N/A'}</li> <li>isCharging: {String(device.battery.isCharging)}</li> <li>languageTag: {device.lang.value}</li> </ul> </div> )} </> ) }一次并发调用四个 API,覆盖插件的全部五类能力中的四类(getInfo / getId / getBatteryInfo / getLanguageTag)。
第 3 步:构建 → 同步 → 编译 HAP
Capacitor 标准三连(注意每次改完前端都要重新 buildui + sync):
hionic buildui# vite build → dist/(233KB JS)hionicsyncopenharmony# dist → rawfile/www/,同时刷新插件注册# 编译鸿蒙工程cdopenharmonyexportDEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdkexportPATH=".../tools/hvigor/bin:.../tools/node/bin:.../tools/ohpm/bin:$PATH"hvigorw assembleHap--modemodule-pmodule=entry@default-pproduct=default\-prequiredDeviceType=phone --no-daemon输出:
> hvigor Finished :entry:default@SignHap... after 917 ms > hvigor BUILD SUCCESSFUL in 4 s 896 ms 41 tasks in total: 32 executed, 9 up-to-date注意这次编译比上次多了Device.cpp的 NAPI 编译任务(32 executed vs 上次 26),签名配置沿用 DevEco 自动签名,直接产出signed HAP。
第 4 步:安装到模拟器并运行
hdc list targets# 127.0.0.1:5555hdcinstall-ropenharmony/entry/build/default/outputs/default/entry-default-signed.hap# [Info] install bundle successfullyhdc shell aa start-aEntryAbility-bcom.nutpi.MyApp# start ability successfully.第 5 步:验证
日志验证(hdc shell hilog -x | grep ARKWEB-CONSOLE)——插件调用真实发生并返回:
ARKWEB-CONSOLE: "Device info: [object Object]" ← Device.getInfo() 成功 ARKWEB-CONSOLE: "Device id: [object Object]" ← Device.getId() 成功截图验证(hdc shell snapshot_display -f /data/local/tmp/cap_device.jpeg+hdc file recv):
页面完整渲染出设备信息:
| 字段 | 模拟器返回值 | 说明 |
|---|---|---|
| name / model | emulator | |
| platform / operatingSystem | HarmonyOS | 插件正确上报鸿蒙平台 |
| osVersion | OpenHarmony-7.0.0.105 | 模拟器 ROM 版本 |
| manufacturer | HUAWEI | |
| isVirtual | true | 正确识别模拟器(真机应为 false) |
| memUsed | 336972 bytes | 应用内存占用 |
四、桥接链路解析
一次Device.getInfo()调用的完整数据流:
React (App.jsx) │ Device.getInfo() ← @capacitor/device 官方 API 层,跨平台一致 ▼ capacitor.js / native-bridge.js ← rawfile/www/ 下的桥接脚本 │ JSON 消息 {plugin:"Device", method:"getInfo"} ▼ ArkWeb Web 组件拦截 → ArkTS 容器层 ▼ capacitor.plugins.json 注册表 → 找到 classpath "Device" ▼ Device.ets(ArkTS)→ Device.cpp(C++/NAPI,编译进 libcapacitor.so) │ 调用 OpenHarmony 系统 API(@ohos.deviceInfo / batteryInfo / i18n) ▼ 结果 JSON 回传 → Promise resolve → React setState → 页面渲染关键点:
- API 契约不变:
getId()在 OHOS 上返回 ODID(OpenHarmony 设备标识),Android 返回 GUID,iOS 返回 UIDevice identifier——语义等价,Web 层无感; - 注册表驱动:
capacitor.plugins.json的pkg/classpath映射是插件查找的依据,这就是为什么手动接入时漏掉第①步会导致插件调用无响应; - 编译双链:ArkTS 文件要进
runtimeOnly.sources,C++ 要进 CMake,漏一个都会在运行时报 “plugin not found” 或编译期报符号缺失。
五、踩坑复盘
本项目插件环节很顺利(hionic 自动化程度高),但有几个易错点值得记录:
| # | 易错点 | 现象 | 规避方法 |
|---|---|---|---|
| 1 | 改了前端忘记 sync | 模拟器上跑的还是旧页面 | 每次 buildui 后必须hionic sync openharmony,再重新 hvigorw 编译 |
| 2 | 混淆 hionic 插件安装方式 | 以为要按 README"手动引入"四步走 | 命令行安装(hionic plugin add)会自动完成四步,手动引入仅用于 hionic 无法处理的特殊插件 |
| 3 | console.log 看不到输出 | ArkWeb 的 console 走 hilog | 用hdc shell hilog -x | grep ARKWEB-CONSOLE查看 WebView 内日志 |
| 4 | 截图时机 | 应用启动有加载过程 | aa start后 sleep 3 秒再snapshot_display |
| 5 | 前置条件 | Device.cpp 编译失败 | 确认上一篇的 openssl(libs + 头文件)已集成——capacitor 原生库与插件共享同一 CMake 工程 |
六、总结
在鸿蒙上给 Capacitor 应用装插件,核心路径可以概括为三步走:
1. 一条命令 ── hionic plugin add @capacitor/xxx,四项工程修改全自动 2. 零改动 ── React 层 import 原包调用,API 与 Android/iOS 完全一致 3. 三连循环 ── buildui → sync → hvigorw,hilog + snapshot 验证以@capacitor/device为样板验证后,CPF-Ionic 清单里的其余 40+ 插件(camera、geolocation、filesystem……)都可按同样路径接入。唯一需要留意的差异是各插件 README 中标注的权限要求(如 geolocation 需在 module.json5 声明定位权限及 reason)——device 插件无需权限,是最适合作为首个验证的插件。
参考文档
- ionic-readme:CPF-Ionic 插件总清单
- CPF-Ionic 组织主页(AtomGit)
- capacitor-device 仓库
- Capacitor Device 插件官方文档