尧图精选

React Native for OpenHarmony 实战:三方库 react-native-device-name 的鸿蒙化适配指南

🕒 发布时间:2026/10/1 8:06:39 📁 来源:尧图网络
react-native-device-name做的事很小读一个用户能看到的设备型号名。埋点上报、设备列表、客服排查、多端登录设备管理这类场景会用到它。它的公开 API 只有一个方法但它是必须做原生适配的那类库——因为它的 JavaScript 只是一层壳。上游的入口文件只有三行import{NativeModules}fromreact-native;const{DeviceName}NativeModules;exportdefaultDeviceName;真正的实现在上游的ios/DeviceName.m和android/src/main/java/com/reactlibrary/DeviceNameModule.java里。没有鸿蒙实现所以在鸿蒙上这个原生模块根本注册不出来。本文讲清四件事怎么快速判断一个库必须有原生实现、适配要补什么、一个返回设备相关非固定值的接口该怎么验证、以及交付包里几处可以补的文档缺口。环境准备本文不重复环境搭建步骤。RNOHReact Native for OpenHarmony开发环境的完整配置见官方开发者指南https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md一、先说结论这是一个「JS 只是壳」的库项结论需要原生适配吗✅需要适配要补什么一个鸿蒙原生模块HAR ArkTS TurboModule补的体量ArkTS 侧8 行HAR 源码工程 9 个文件、预编译产物2.6 KB需要权限吗❌ 不需要公开 API只有 1 个getDeviceName(): Promisestring判断依据很直接看package.json有没有harmony.autolinking。harmony:{alias:react-native-device-name,autolinking:{ohPackageName:react-native-ohos/react-native-device-name,etsPackageClassName:DeviceNamePackage,cppPackageClassName:DeviceNamePackage,cmakeLibraryTargetName:rnoh_device_name}}二、判定过程怎么确认必须做原生适配选库阶段就能判断不用先装进来。第一步看package.json有没有harmony字段npmview包名harmony--json有harmony.autolinkingohPackageName/etsPackageClassName/cppPackageClassName/cmakeLibraryTargetName那四个名字的一定是带原生实现的包适配时要做link-harmony ohpm hvigor 三件事。没有这个字段的可能是纯 JS 库也可能像本例一样——上游没做鸿蒙适配所以字段是后加的。所以要继续看第二步。第二步看上游包里有哪些平台的实现$npmpack react-native-device-name1.0.0 $tar-xzfreact-native-device-name-1.0.0.tgz $lspackage index.js package.json README.md android/ ios/ react-native-device-name.podspecandroid/和ios/两个目录都在但没有harmony/—— 这就是必须补一个鸿蒙实现的直接证据。第三步读入口文件看它到底在做什么// package/index.js —— 上游的全部 JSimport{NativeModules}fromreact-native;const{DeviceName}NativeModules;exportdefaultDeviceName;入口里没有任何算法只是在取一个原生模块。这类库的本体是原生实现JS 那层换个平台就得靠新的原生模块顶上去。反过来如果入口里是完整的业务逻辑字符串处理、编解码、状态机只是偶尔Platform.OS ios分叉一下——那通常是纯 JS 库 平台差异优先考虑在 JS 侧解决不一定要写原生。三、适配实现取值链与协作方式交付版新增的harmony/device_name/结构harmony/device_name/ ├── Index.ets # 导出 DeviceNamePackage ├── oh-package.json5 # 声明包名 react-native-ohos/react-native-device-name ├── build-profile.json5 └── src/main/ ├── module.json5 ├── cpp/ # CAPI 架构下的 C 侧只是个注册壳 │ ├── CMakeLists.txt │ ├── DeviceNamePackage.h # Package TurboModule 工厂 methodMap │ └── DeviceNamePackage.cpp └── ets/ ├── DeviceNamePackage.ets # 把 TurboModule 交给 RNOH └── DeviceNameTurboModule.ts # ★ 全部实现逻辑ArkTS 侧的全部实现只有一行取值逻辑import{UITurboModule}fromrnoh/react-native-openharmony/ts;importdeviceInfofromohos.deviceInfo;exportclassDeviceNameTurboModuleextendsUITurboModule{asyncgetDeviceName():Promisestring{returndeviceInfo.marketName||deviceInfo.productModel||OpenHarmony device;}}取值链marketName→productModel→ 兜底文本OpenHarmony device。marketName是面向用户的市场名真机上通常是 “HUAWEI Mate 60” 这类业务展示应该优先用它productModel是产品型号在模拟器或未配置市场名的设备上更可能有值最后一层是兜底保证接口永远返回非空字符串——因为契约是Promisestring、调用方通常直接展示。ohos.deviceInfo读的是系统公开参数不需要任何权限。C 侧只是个注册壳一行业务逻辑都没有classDeviceName:publicArkTSTurboModule{public:DeviceName(constArkTSTurboModule::Context ctx,conststd::stringname):ArkTSTurboModule(ctx,name){methodMap_{ARK_ASYNC_METHOD_METADATA(getDeviceName,0),};}};ARK_ASYNC_METHOD_METADATA(getDeviceName, 0)—— 方法名、0 个参数、返回 Promise。方法名或参数个数写错表现是编译过了但调用报方法不存在。JS 侧的改动把undefined隐患换成早失败交付版的src/index.ts相比上游换了取模块的方式import{TurboModuleRegistry,typeTurboModule}fromreact-native;exportinterfaceSpecextendsTurboModule{getDeviceName():Promisestring;}exportdefaultTurboModuleRegistry.getEnforcingSpec(DeviceName);上游是const { DeviceName } NativeModules——模块没注册时得到undefined要等到第一次调用才炸。交付版用TurboModuleRegistry.getEnforcing模块缺失时在导入阶段就抛出明确错误。这个改动很小但对排障很有用接入出错时你会在启动日志里立刻看到而不是在业务跑到某个分支时才报Cannot read property getDeviceName of undefined。四、接入宿主三处改动面外加一处自动生成的库本身不能独立运行必须有一个 RNOH 宿主 App。这里用社区现成的RNOH084DemoRNOH 0.84.3 的多库验证宿主它自带一个rnAppKey机制一个宿主可以挂很多独立测试页// harmony/entry/src/main/ets/entryability/EntryAbility.etsconstrnAppKeywant.parameters?.[rnAppKey]asstring|undefined;AppStorage.setOrCreate(rnAppKey,rnAppKey??RNOH084Demo);于是可以用命令行参数切换测试页hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey DeviceNameTestApp接入要改的地方第一处package.json。react-native-device-name:file:../react-native-device-name第二处两级oh-package.json5都要写 HAR。react-native-ohos/react-native-device-name: file:../node_modules/react-native-device-name/harmony/device_name.har,harmony/oh-package.json5管工程级、harmony/entry/oh-package.json5管模块级两处都要加。只加一处会出现能找到包但链接不上。这里有个很容易漏的点跑link-harmony时它只会自动更新工程级那一份模块级那份要你自己加。第三处在 ETS 侧注册 Package。// harmony/entry/src/main/ets/RNOHPackagesFactory.etsimporttype{RNPackageContext,RNOHPackage}fromrnoh/react-native-openharmony;importDeviceNamePackagefromreact-native-ohos/react-native-device-name;exportfunctioncreateRNOHPackages(ctx:RNPackageContext):RNOHPackage[]{return[newDeviceNamePackage(ctx),];}代码写在哪这里说清楚手工改动面就是这三个文件外加metro.config.js的watchFolders。C 侧不用手改——CAPI 架构下PackageProvider.cpp会自动消费 autolinking 生成的RNOHPackagesFactory.h。那自动生成的一处是什么执行link-harmony时它会一次性重写这四个文件• harmony/entry/src/main/cpp/RNOHPackagesFactory.h # C 侧注册 • harmony/entry/src/main/cpp/autolinking.cmake # add_subdirectory 链接 • harmony/entry/src/main/ets/RNOHPackagesFactory.ets # ETS 侧注册 • harmony/oh-package.json5 # 工程级 HAR 依赖这四个文件头部都写着DO NOT modify it manually, your changes WILL be overwritten.——别手改。接入成功的两个自检信号$ node_modules\.bin\react-native link-harmony --verbose [link] react-native-device-name [skip] react-native-oh/react-native-harmony ... info updated 4 file(s), linked 6 libraries, skipped 1 library[link] react-native-device-name在列表里说明 autolinking 认出了它的harmony.autolinking。打包时 Metro 也会把它列进重定向清单[INFO] Redirected imports to 6 harmony-specific third-party package(s): [INFO] • react-native-device-name → react-native-device-name …这两个信号对这个库是必须出现有原生实现的包要被解析到它的鸿蒙实现列表里没有它就说明没接通。harmony/entry/src/main/module.json5未改动——本库不读任何需要权限的信息不用加requestPermissions。五、构建与运行# 1) 装 JS 依赖 自动链接npminstall./node_modules/.bin/react-native link-harmony# 2) 生成调试签名 装 ohpm 依赖cdharmony devecocli signature generate ohpminstall--all# 3) 打包 JS bundle输出到 harmony/entry/src/main/resources/rawfile/cd..npmrun dev# 4) 编译 HAPcdharmony hvigorw--modemodule-pproductdefault-pmoduleentrydefault assembleHap --no-daemon# 5) 安装 启动测试页hdcinstall-rentry/build/default/outputs/default/entry-default-signed.hap# 换页参数只在「冷启动」时生效先 force-stop 再起hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey DeviceNameTestApp耗时在已有原生编译缓存的宿主上增量加入这个库assembleHap用了 6 分 35 秒HAP 从 80.09 MB 涨到 80.23 MB约 136 KB。如果宿主是全新 clone没有原生编译缓存首次构建会到30–40 分钟量级只改 JS 重新打包也要 6–7 分钟所以别把 UI 微调留到最后做。看日志hdc shellhilog -x | grep -i TM createdhdc shellhilog -x | grep -i device-name-test看界面读无障碍树不用截图就能拿到文本devecocli ui layout按文本点击页面高度会随结果卡片变化别记固定坐标.E:\rnoh-work\click-label.ps1 Click-Label-Pattern读取设备名并跑全部断言六、验证设计设备相关的非固定值怎么验这个库只有一个方法而且返回值不是固定值——它取决于跑在什么设备上。这就带来一个验证陷阱如果只断言返回非空字符串那么返回a、随便什么、OpenHarmony device都会通过——等于没验证。所以验证要回答两个不同的问题问题怎么验接口形态对不对返回 Promise、类型是 string、非空、无空白/控制字符、长度合理、多次调用一致返回值对不对与设备系统参数交叉核验期望值由外部独立读出期望值从哪来用hdc独立读系统参数$ hdc shell param get const.product.marketname Get parameterconst.product.marketnamefail!errNum is:106!← 不存在 $ hdc shell param get const.product.model emulator $ hdc shell param get const.product.name emulator $ hdc shell param get const.product.brand HUAWEI $ hdc shell param get const.product.os.dist.name HarmonyOS $ hdc shell param get const.product.os.dist.apiname26.0.0 $ hdc shell param get const.product.software.version.name HarmonyOS NEXT Developer Beta1这一步是整个验证的关键期望值来自系统本身param get不是从被测库拿的。如果库返回一个自己编的字符串它不可能同时等于const.product.model和const.product.name。而且这次还额外覆盖到了回退分支本机const.product.marketname不存在也就是deviceInfo.marketName为空所以实现必然走第二条分支取productModel→ 期望返回值是emulator。⇒ 一次验证同时确认了两件事值是对的等于系统参数给出的productModel回退分支真的生效了marketName为空时确实回退而不是返回空串或抛错。七、真机验证验证环境Pura X View模拟器HarmonyOS 7.0.0(26.0.0) Beta2API 26ohos-x64。TurboModule 注册RNInstance::TurboModuleProvider TM created: DeviceName断言结果10 / 10 全部通过A 组契约与形态7 项断言结果返回 Promisetypeof .then function✅返回类型是string✅非空字符串✅无首尾空白✅无控制字符/[\u0000-\u001f\u007f]/不匹配✅长度不超过 64✅连续三次调用结果一致✅B 组值正确性3 项断言结果等于const.product.modelemulator✅等于const.product.nameemulator✅未落到兜底文本OpenHarmony device✅原始读数与耗时[device-name-test] getDeviceName() 连续三次emulator / emulator / emulator [device-name-test] 单次耗时31 ms / 4 ms / 0 ms [device-name-test] A B契约 / 形态 / 值正确性 - 10/10 全部通过首次调用31 ms含 TurboModule 创建与 ArkTS 侧首次加载ohos.deviceInfo之后4 ms / 0 ms。这是个读一次就能缓存的接口业务侧没必要反复调用。能力对照能力结果getDeviceName()返回 Promise✅返回非空字符串✅返回值等于设备系统参数中的productModel✅ 独立交叉核验通过marketName为空时的回退分支✅ 本机实测走的就是这条分支需要权限✅ 不需要宿主未新增任何requestPermissionsTurboModule 注册✅TM created: DeviceName八、交付包可补的地方运行时没有问题但交付包在可追溯性上有三处缺口如实记下来一、README 里的上游地址是占位符。README.OpenHarmony_CN.md/README.OpenHarmony.md写的都是github_account/react-native-device-name——这不是真实上游地址读者按 README 找不到源仓库。应该换成上游真实地址。二、spec.json没记录上游仓库与基线 commit。现有一份spec.json只有name/version/module/native/description/methods/rnoh/reactNative/validation没有upstream字段、也没有upstreamCommit。对交付包来说“我验的是上游哪一版是核心信息——缺了它就无法复现也无法回答上游发新版后这版适配是否还对应”。三、契约测试没有真正测到东西。consttestrequire(node:test);constassertrequire(node:assert/strict);test(public contract returns a promise of a non-empty device name,(){assert.equal(typeofDeviceName,string);assert.equal(typeofPromise.resolve(OpenHarmony device).then,function);});这两条断言的对象是字面量——typeof DeviceName永远是stringtypeof Promise.resolve(...).then永远是function。测试没有 import 本库也没有调用任何方法所以无论库是否可用都会通过。这不是验证只是占位。根因推测上游 npm 包本身就是没改过的 RN 库模板——package/package.json里description: TODO、author: {name: Your Name, email: yournameemail.com}、repository.url指向github_account/...。交付时把上游的占位符照抄进了 README也没另外补spec.json的上游字段。顺带一个上游打包问题上游 tarball92,757 字节里塞满了构建产物——android/build/、android/.gradle/、android/.idea/其中gradle_models.ser56 KB、executionHistory.bin32 KB。这是上游.npmignore配置的问题不是交付方引入的但会让包体积膨胀十几倍。九、已知限制一、只读系统公开字段不读用户自定义名称。实现取的是deviceInfo.marketName/deviceInfo.productModel不是用户在设置里改过的设备名。如果你的业务需要用户给设备起的名字这个库不满足——那通常要调设置类接口而且往往需要权限。二、不同 ROM 的字段取值可能不同。const.product.marketname/const.product.model是否存在、格式如何取决于 ROM。本次只在一台模拟器上验证实测marketName为空、回退到productModel。三、回退到兜底文本的分支未实测。只有marketName与productModel同时为空时才会返回OpenHarmony device。模拟器上至少有一个有值无法构造这个场景——所以兜底文本这条路径只有代码层面可见没有设备侧证据。四、真机上的返回值很可能与模拟器不同。真机通常有市场名如 “HUAWEI Mate 60”届时返回的会是marketName而不是productModel。业务展示应该允许名称变化不要把它当作稳定标识符做逻辑判断。五、返回值不是设备唯一标识。同一个型号名会出现在成千上万台设备上。它只适合展示不适合做设备身份——需要唯一标识时应该用别的方案。六、未测大并发与高频调用。接口本身没有缓存每次调用都会走过桥。实测首次 31 ms、后续 0–4 ms业务侧建议自己缓存结果不要放在高频渲染路径里。七、其他 ROM / 真机未验证。适配方记录的是 OpenHarmony-7.0.0.105本次在 HarmonyOS 7.0.0(26.0.0) Beta2 模拟器上通过。十、常见问题Q这个库为什么必须做原生适配A它的 JS 入口只有三行——export default NativeModules.DeviceName本体是原生实现。上游只提供了ios/DeviceName.m和android/.../DeviceNameModule.java没有鸿蒙实现。不补一个鸿蒙原生模块NativeModules.DeviceName就是undefined调用必然失败。Q怎么快速判断一个库要不要做原生适配A三步。①npm view 包名 harmony --json看有没有harmony.autolinking有 → 一定是带原生实现的适配包② 上游包里有哪些平台的实现目录只有ios/android/而没有harmony/→ 要补一个鸿蒙实现③ 读入口文件——如果里面只有取原生模块而没有任何业务逻辑那它天生依赖原生。Q适配一共要改多少东西AArkTS 侧的实现逻辑只有一行deviceInfo.marketName || deviceInfo.productModel || OpenHarmony device加上 Package/CMake/Index 的接线一共 9 个文件预编译 HAR 只有 2.6 KB。接入宿主时手工改动面是三处package.json依赖、模块级oh-package.json5加 HAR、RNOHPackagesFactory.ets注册 Package。Q为什么返回的是emulator而不是设备市场名A因为实现的取值链是marketName → productModel → 兜底文本而这台模拟器的marketName为空hdc shell param get const.product.marketname返回不存在所以回退到了productModelemulator。真机上通常有市场名返回值会不同——这也是为什么验证不能只断言非空。Q怎么验证一个返回值不固定的接口A把验证拆成两层①形态层——返回 Promise、类型、非空、无空白/控制字符、多次一致②值层——找一个外部独立来源作为期望值。本例用的是hdc shell param get const.product.model从系统本身读出期望值而不是从被测库拿。如果库返回的是自己编的字符串它不可能同时等于两个不同的系统参数。Q返回的设备名能当唯一标识用吗A不能。同一个型号名会出现在大量设备上。它只适合展示。需要唯一标识请用别的方案。Q能不能读用户自己改的设备名A不能。实现只读ohos.deviceInfo的系统公开字段不读用户自定义名称也不需要任何权限。业务如果依赖用户自定义名称得另找接口。Qspec.json里没有上游 commit会不会有问题A不影响运行但影响可追溯性。交付包的价值之一是能回答我验的是上游哪一版、上游发新版后这版是否还对得上。缺upstream/upstreamCommit就答不了。README 里那个github_account/...也是占位符建议一并补上。Q为什么我在模拟器上编译要这么久A宿主已有原生编译缓存时增量加一个库约6–7 分钟hvigor 会把整套流水线走一遍全新克隆的宿主首次编译要 30–40 分钟。只改 JS 重新打包也是 6–7 分钟。小结这个库的适配体量是一行取值 三层接线但有两件事值得记第一件是判定。一个 RN 三方库要不要做原生适配看三点就够了package.json有没有harmony.autolinking、上游包里有哪些平台的实现目录、入口文件里有没有真实业务逻辑。本例的入口只有三行、只是在取一个原生模块——这类库的本体在原生侧换个平台就必须补实现。判断对了就清楚知道工作量在哪判断错了比如把一个JS 只是壳的库当纯 JS 用会在运行时才发现模块是undefined。第二件是非固定返回值的验证设计。这个库只有一个方法、返回一个随设备变化的字符串。只断言非空等于没验证——任何字符串都能过。所以要把验证拆成两层形态层查契约值层找外部独立来源做交叉核验。本例用的是hdc shell param get期望值由系统读出不经由被测库。这样即使库返回一个自造的字符串也无法同时等于两个不同的系统参数。而且这次运气不错本机marketName恰好为空于是实测正好走过了回退到productModel这条分支——一次验证同时确认了值是对的和回退逻辑真的生效。验证一个带取值链的接口时先弄清每条分支的触发条件再挑一个能触发非首条分支的环境比只测 happy path 有价值得多。最后如实记了交付包的三处文档缺口README 里上游地址还是占位符、spec.json缺上游 commit、契约测试断言的是字面量没 import 库、没调用方法怎么跑都会通过。这三条不影响运行时但影响这个交付包能不能被信任和复现——一个测试如果永远不会失败它就不是测试。本篇用到的库项内容三方库react-native-device-name上游 1.0.0 的鸿蒙适配版交付仓库https://atomgit.com/oh-react-native/react-native-device-name适配 TAG1.0.0-ohos-1.0.0ohpm 包名react-native-ohos/react-native-device-nameHARharmony/device_name.har2.6 KB原生模块名DeviceName是否需要权限不需要宿主工程RNOH084Demo测试页rnAppKey DeviceNameTestAppreact-native-device-name:githttps://atomgit.com/oh-react-native/react-native-device-name.git#1.0.0-ohos-1.0.0// harmony/oh-package.json5 与 harmony/entry/oh-package.json5 都要加 react-native-ohos/react-native-device-name: file:../node_modules/react-native-device-name/harmony/device_name.har,importDeviceNamefromreact-native-device-name;constnameawaitDeviceName.getDeviceName();// 例如 emulator / HUAWEI Mate 60# 换页启动测试页force-stop 不能省换页参数只在冷启动生效hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey DeviceNameTestApp验证环境项版本React Native0.84.1React19.2.3RNOHnpm / ohpmreact-native-oh/react-native-harmony/rnoh/react-native-openharmony0.84.3Node.jsv24.14.0DevEco Studio26.0.0.621HarmonyOS SDKAPI 2626.0.0.32设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器Pura X Viewohos-x64本机const.product.modelemulator本机const.product.marketname不存在errNum 106宿主 HAP 产物entry-default-signed.hap80.23 MB本次增量构建assembleHap6 分 35 秒验证规模设备侧10 项断言全通过 与系统参数交叉核验欢迎加入 CPF-RN 鸿蒙社区https://atomgit.com/CPF-RNReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview
上一篇/下一篇内容由系统自动关联 返回资讯列表 →