设备信息获取:@ionic-native/device 插件在鸿蒙 OpenHarmony 平台的适配实践
使用 hionic CLI 在 React + Capacitor 项目中集成设备信息获取
背景
在移动端开发中,获取设备信息是高频需求——用于兼容性适配(不同屏幕尺寸、系统版本)、用户行为分析(设备型号分布)以及安全校验(设备唯一标识)。
@ionic-native/device 插件理论上提供全局 device 对象,可获取设备型号、操作系统、唯一标识符、制造商等硬件与软件环境信息。但在 hionic(鸿蒙 Capacitor 桥接框架)上,这个插件的适配遇到了不少陷阱,本文将完整记录从安装到最终通过纯 JS 方案拿到设备信息的全过程。
项目开源地址:🔗 https://gitcode.com/jianguoxu/hionic
1. 安装插件
hionic plugin add @ionic-native/device
安装流程:npm 安装 → OHOS 检测 → config.xml 注册 → JS 桥接文件生成 → C++/ArkTS 原生代码下载。
日志中会看到类似信息:
> hionic plugin add @ionic-native/device
[i-ohos-plugin]: 目标不存在: /openharmony/cordova
...
[i-ohos-plugin]: copy folder to openharmony/cordova ...
2. 踩坑一:原生代码模块名不匹配
问题
插件的原生代码安装在 openharmony/cordova/ 目录下,但项目使用的模块名为 capacitor(openharmony/capacitor/)。
解决
手动将 C++ 和 ArkTS 代码复制到 capacitor 模块:
cp -r oh-plugins/cordova-plugin-device/src/main/cpp/Device \
openharmony/capacitor/src/main/cpp/Device
cp -r oh-plugins/cordova-plugin-device/src/main/ets/components/Device \
openharmony/capacitor/src/main/ets/components/Device
rm -rf openharmony/cordova
确认 CMakeLists.txt 包含 Device/Device.cpp。
3. 踩坑二:Cordova 桥 vs Capacitor 桥 — 双桥架构的缝隙
问题
安装完成后,前端 window.device 始终为 undefined。调试发现:
cordovaExists: true
cordovaExecExists: false
cordovaKeys: "define, require, addWindowEventHandler"
capacitorPlugins: "Camera, TsCapacitorPlugin"
cordova.exec 不存在,Device 插件也未出现在 Capacitor.Plugins 中。
根因分析
hionic 的架构存在两条平行的原生桥:
| 桥 | 注册宏 | JS 调用方式 |
|---|---|---|
| Cordova 桥 | REGISTER_PLUGIN_CLASS(Device) |
cordova.exec(...) 或 cordova.require('cordova/exec') |
| Capacitor 桥 | REGISTER_CAP_PLUGIN(Device, Device) |
Capacitor.nativeCallback('Device', 'getDeviceInfo', {}, callback) |
@ionic-native/device 的原生端代码(Device.cpp)只注册了 Cordova 桥:
// Device.cpp
REGISTER_PLUGIN_CLASS(Device) // 只注册了 Cordova 桥
而 @capacitor/camera 注册的是 Capacitor 桥:
// Camera.cpp
REGISTER_CAP_PLUGIN(Camera, Camera) // Capacitor 桥
REGISTER_PLUGIN_METHOD(Camera, getPhoto, PluginMethod::RETURN_PROMISE)
hionic 的 WebView 通过 Capacitor 桥的 nativeCallback/nativePromise 与原生层通信,Cordova 桥注册的函数对 Capacitor 桥不可见。
理论上可行的修复
给 Device.cpp 加上 Capacitor 桥注册(但装置复杂,见下节):
#include "getcapacitor/Plugin.h"
REGISTER_CAP_PLUGIN(Device, Device)
REGISTER_PLUGIN_METHOD(Device, getDeviceInfo, PluginMethod::RETURN_PROMISE)
4. 踩坑三:Cordova 模块系统不兼容 — 更深的坑
问题
即使尝试用 cordova_plugins.js 通过 cordova.define('Device', ...) 注册 Device 插件,window.device 依然不存在。
根因分析
@capacitor/core/cordova.js 的模块系统工作流程:
cordova.js 加载
├─ 创建本地 define/require(闭包内变量)
├─ define('cordova', ...) ← 含 define/require 属性
├─ define('cordova/exec', ...)
├─ ...
├─ 同步执行:
│ ├─ modulemapper.clobbers('cordova', 'cordova') ← 只注册不执行
│ ├─ modulemapper.clobbers('cordova/exec', 'cordova.exec')
│ ├─ platform.bootstrap() → channel.onNativeReady.fire()
│ └─ setTimeout(0) → pluginloader.load()
│ └─ require('cordova/plugin_list') ← 抛出异常!
│ ↑ 因为 cordova_plugins.js 加载时
│ window.cordova.define 还不存在
└─ channel.join([onNativeReady, onPluginsReady])
↑ onPluginsReady 永不触发 → join 永不完成
→ modulemapper.mapModules(window) 永不执行
→ window.cordova = require('cordova') 永不执行
→ window.device 永不创建
关键问题有两个:
-
cordova.define不在window.cordova上:@capacitor/core/cordova.js的define函数定义在 IIFE 闭包内,window.cordova.define只有通过异步的modulemapper.mapModules(window)才会赋值。同步的<script>标签加载cordova_plugins.js时,window.cordova.define不存在。 -
cordova/exec在 OpenHarmony 上不可用:@capacitor/core/cordova.js的capacitorExec函数只检测window.androidBridge(Android)和window.webkit.messageHandlers.bridge(iOS),OpenHarmony 两者都没有。
最终方案:放弃 Cordova 模块系统
既然 Cordova 模块系统在 OpenHarmony 上无法正常工作,直接改用纯 JavaScript 方案获取设备信息:
// cordova_plugins.js — 直接在脚本执行时设置 window.device
(function () {
var info = {};
// 平台:通过 Capacitor API 或 User-Agent
try {
if (window.Capacitor && typeof window.Capacitor.getPlatform === 'function') {
info.platform = window.Capacitor.getPlatform();
}
} catch (e) {}
if (!info.platform) {
var ua = navigator.userAgent || '';
if (ua.indexOf('HarmonyOS') >= 0) info.platform = 'OpenHarmony';
else info.platform = 'browser';
}
// 型号:从 User-Agent 解析
var ua = navigator.userAgent || '';
var modelMatch = ua.match(/; ([^;]+?)(?:;|\))/);
info.model = modelMatch ? modelMatch[1].trim() : '';
info.manufacturer = info.model ? info.model.split(' ')[0] : '';
// UUID(localStorage 持久化)
try {
info.uuid = localStorage.getItem('device_uuid');
if (!info.uuid) {
info.uuid = 'DEVICE-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 8);
localStorage.setItem('device_uuid', info.uuid);
}
} catch (e) { info.uuid = 'unknown'; }
info.version = '';
info.isVirtual = false;
info.cordova = '1.0.0';
info.available = true;
window.device = info;
// 触发 deviceready 事件
try {
document.dispatchEvent(new Event('deviceready'));
} catch (e) {}
})();
同时,前端代码也增加浏览器 API 降级逻辑,不依赖 window.device:
useEffect(() => {
var platform = ''
try { if (window.Capacitor) platform = Capacitor.getPlatform() } catch(e) {}
var ua = navigator.userAgent || ''
var modelMatch = ua.match(/; ([^;]+?)(?:;|\))/)
var model = modelMatch ? modelMatch[1].trim() : 'Unknown'
var manufacturer = model.indexOf(' ') > 0 ? model.split(' ')[0] : ''
var info = [
{ label: 'Model', value: model },
{ label: 'Platform', value: platform || 'unknown' },
{ label: 'Manufacturer', value: manufacturer || 'unknown' },
]
// 如果 window.device 已设置(来自 cordova_plugins.js),优先使用
if (window.device && window.device.available) {
if (window.device.model) info[0].value = window.device.model
if (window.device.platform) info[1].value = window.device.platform
if (window.device.uuid) info.push({ label: 'UUID', value: window.device.uuid })
}
setDeviceInfo(info)
}, [])
5. 踩坑四:WebView 加载路径错误
问题
多次部署后设备信息仍然显示不出来。检查 HAP 包内容发现:
resources/rawfile/index.html ← 我一直在更新的文件(capacitor 模块)
resources/rawfile/www/index.html ← WebView 实际加载的文件(entry 模块)
Index.ets 中的注释明确写着:
// 默认加载 rawfile/www/index.html
而我之前一直在更新 capacitor/src/main/resources/rawfile/index.html(对应 HAP 中的 resources/rawfile/index.html),WebView 从未读到我的修改。
解决
更新正确的路径:
# 正确路径
openharmony/entry/src/main/resources/rawfile/www/cordova_plugins.js
openharmony/entry/src/main/resources/rawfile/www/index.html
openharmony/entry/src/main/resources/rawfile/www/assets/
# 错误路径(之前一直在改的)
openharmony/capacitor/src/main/resources/rawfile/index.html
6. 踩坑五:Platform 检测错误 - 显示"Android"而非"OpenHarmony"
问题
设备信息终于显示出来了,但 Platform 显示为 Android,而非预期的 OpenHarmony。
根因分析
检查 hionic 的 C++ 桥接代码 MessageHandler.cpp:
// openharmony/capacitor/src/main/cpp/getcapacitor/MessageHandler.cpp
ArkWeb_ProxyObject proxyObject = {"androidBridge", methodList, 1};
hionic 在 WebView 中注册的 JavaScript 代理对象名为 androidBridge —— 这是为了与标准 Capacitor 的 WebView 检测逻辑兼容。而 native-bridge.js 中的平台检测函数:
const getPlatformId = (win) => {
if (win?.androidBridge) return 'android'; // ← hionic 匹配这里!
else if (win?.webkit?.messageHandlers?.bridge) return 'ios';
else return 'web';
};
因为 window.androidBridge 存在,Capacitor.getPlatform() 返回 'android'。
解决
在 cordova_plugins.js 中,利用 hionic 特有的 TsCapacitorPlugin 作为判断依据:
function isHionic() {
try {
return !!(window.Capacitor && window.Capacitor.Plugins &&
window.Capacitor.Plugins.TsCapacitorPlugin);
} catch(e) { return false; }
}
if (isHionic() || navigator.userAgent.indexOf('HarmonyOS') >= 0) {
info.platform = 'OpenHarmony';
} else {
info.platform = Capacitor.getPlatform();
}
TsCapacitorPlugin 是 hionic 特有的 Capacitor 插件(标准 Capacitor 中没有),因此是判断 OpenHarmony 环境的可靠标志。
7. 完整发布流程
# 1. 构建前端
npm run build
# 2. 复制桥接文件和构建产物到 entry 模块的 rawfile/www/
WWW=openharmony/entry/src/main/resources/rawfile/www
cp cordova_plugins.js "$WWW/"
cp node_modules/@capacitor/core/cordova.js "$WWW/"
cp -r dist/assets/* "$WWW/assets/"
# 3. 构建 HAP(注意 Node.js 版本兼容)
cd openharmony
NODE_HOME=/Applications/DevEco-Studio.app/Contents/tools/node \
hvigorw assembleApp --no-daemon -p product=default
# 4. 安装运行
hdc install entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.nutpi.MyApp
8. 构建环境注意
DevEco Studio 内置 Node.js v18.20.1,而 Homebrew 安装的 Node.js v26 与 hvigor 存在兼容性问题:
Error Code: 00308018 Unknown Error
The property 'options.recursive' is no longer supported.
原因:Node.js v20+ 移除了 fs.rmdirSync(path, { recursive: true }) 的 options.recursive 格式,v26 中直接报错。DevEco Studio 的 hvigor 插件仍使用旧格式。
解决:设置 NODE_HOME 环境变量使用 DevEco 内置 Node:
export NODE_HOME=/Applications/DevEco-Studio.app/Contents/tools/node
9. 三种插件调用对比
| 插件 | 类型 | 调用方式 | OpenHarmony 状态 |
|---|---|---|---|
@capacitor/camera |
Capacitor | await import('@capacitor/camera') |
✅ 原生 C++ 工作正常 |
@ionic-native/in-app-browser |
Cordova | window.InAppBrowser.create() |
✅ 原生 C++ 工作正常 |
@ionic-native/device |
Cordova | window.device |
⚠️ 原生 C++ 桥不可用,改用纯 JS 方案 |
10. 要点总结
| 要点 | 说明 |
|---|---|
| 双桥架构 | hionic 同时支持 Cordova 和 Capacitor 桥,但插件只注册一个桥时另一个不可见 |
| Cordova 模块系统不兼容 | @capacitor/core/cordova.js 的 define 函数闭包隔离,同步脚本无法使用 |
cordova.exec 在 OHOS 不可用 |
只支持 Android/iOS 的原生消息通道 |
| WebView 加载路径 | 始终确认 entry/src/main/resources/rawfile/www/ |
| Node.js 版本兼容 | 用 DevEco 内置 Node.js v18 构建 |
androidBridge 误判 |
桥接注册为 androidBridge,getPlatform() 返回 'android',需用 TsCapacitorPlugin 二次判断 |
| 最终方案 | 纯 JS 解析 Capacitor.getPlatform() + TsCapacitorPlugin 检测 + navigator.userAgent 降级 |
参考资源