ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

React Native for OpenHarmony 实战:三方库 react-native-torch 的鸿蒙化适配指南 本文记录把react-native-torch手电筒 / 相机闪光灯控制适配到 HarmonyOS 的完整过程包括版本对齐、原生实现走查、接入宿主、构建运行以及依赖硬件的库在模拟器上到底该怎么验证。react-native-torch的 API 只有两个却是个典型例子它控制的是一块模拟器上根本不存在的硬件。这种库的适配难点不在代码量而在什么算验证通过。一、先说结论项结果上游最新版1.2.0是否带原生实现是ArkTS TurboModule不是纯 JS是否依赖硬件是依赖相机闪光灯公开 APIswitchState(enabled)、requestCameraPermission(title?, message?)都返回 Promise是否需要声明权限不需要连ohos.permission.CAMERA都不用原生实现体量ArkTS 侧19 行HAR 包2,875 字节编译是否通过✅assembleHap成功HAP 从 80,714,316 增至80,867,227 字节152,911 ≈ 149 KB原生是否注册✅ 日志TM created: RCTTorch软件层断言✅13 / 13 通过契约 6 参数守卫 7硬件层结论⚠️本机没有闪光灯四次调用全部以ERR_TORCH_UNAVAILABLE拒绝——没有返回true假装成功一句话结论这个库不需要权限、实现只有 19 行、接入后正常注册但它要控制的闪光灯在这台设备上不存在所以灯亮不亮无法在本轮验证——本轮真正验证到的是它在硬件不支持时会明确报错而不是假装成功。这两件事必须分开说。二、判定过程适配前先回答三个问题。2.1 这个库需要鸿蒙化吗看源码就知道必须// src/NativeTorch.tsimporttype{TurboModule}fromreact-native;import{TurboModuleRegistry}fromreact-native;exportinterfaceSpecextendsTurboModule{switchState(enabled:boolean):Promiseboolean;requestCameraPermission(title?:string,message?:string):Promiseboolean;}exportdefaultTurboModuleRegistry.getEnforcingSpec(RCTTorch);getEnforcing意味着取不到原生模块就直接抛错没有 JS 兜底实现。而react-native-torch上游只提供 Android 与 iOS 原生代码鸿蒙上没有RCTTorch这个名字的模块——所以必须补一个鸿蒙原生实现。这里和纯 JS 库的分界线很清楚纯 JS 库只要能跑通就直接可用这类库不补原生就必然报错。2.2 上游版本核对npmview react-native-torch version# 1.2.0拿到鸿蒙适配包后对比到上游基线版本一致没有落后。2.3 交付包里该有的信息都在spec.json里有upstream仓库地址也有upstreamCommit基线 commit——两样都记了意味着后续上游改动时能确定该合到哪个点这次适配的可追溯性是完整的。validation里还明确写了能力边界其中一条关键返回true不表示设备一定有闪光灯。交付包自己先把这件事说清楚了这一点很重要——它没有让调用方误以为switchState返回true 灯亮了。三、适配实现先查支持性再下发命令原生实现全文// harmony/torch/src/main/ets/RCTTorchTurboModule.tsimportcamerafromohos.multimedia.camera;import{UITurboModule}fromrnoh/react-native-openharmony/ts;exportclassRCTTorchTurboModuleextendsUITurboModule{asyncswitchState(enabled:boolean):Promiseboolean{constmanagercamera.getCameraManager(this.ctx.uiAbilityContext);constmodeenabled?camera.TorchMode.ON:camera.TorchMode.OFF;if(!manager.isTorchSupported()||!manager.isTorchModeSupported(mode)){thrownewError(ERR_TORCH_UNAVAILABLE: flashlight mode is not supported);}manager.setTorchMode(mode);returntrue;}asyncrequestCameraPermission():Promiseboolean{// HarmonyOS CameraManager torch APIs do not require CAMERA permission// or open a camera input.returntrue;}}三个设计点值得展开。3.1 支持性检查必须在下发命令之前if(!manager.isTorchSupported()||!manager.isTorchModeSupported(mode)){thrownewError(ERR_TORCH_UNAVAILABLE: ...);}manager.setTorchMode(mode);这里做了两层检查isTorchSupported()—— 这台设备有没有闪光灯isTorchModeSupported(mode)—— 这个模式ON / OFF支不支持。两层是必要的因为有些设备的闪光灯只支持常亮、不支持闪烁或反过来“有闪光灯不等于这个模式可用”。检查失败时抛的是带前缀的明确错误ERR_TORCH_UNAVAILABLE调用方能靠字符串识别并走降级逻辑。这一点比静默返回 false好得多——静默失败会让调用方以为操作成功了。3.2 它不打开相机、不采集图像实现里出现的相机 API 只有四个getCameraManager / isTorchSupported / isTorchModeSupported / setTorchMode没有createCameraInput、没有createCaptureSession、没有预览流。这是静态就能查清的事实也是理解为什么不需要CAMERA权限的钥匙——它只借用CameraManager的手电接口不碰相机采集链路。3.3 JS 侧的参数守卫是被拒绝的 Promise上游 JS 层保留了类型校验asyncfunctionswitchState(enabled:boolean):Promiseboolean{if(typeofenabled!boolean){thrownewTypeError(Torch.switchState expects a boolean);}returnNativeTorch.switchState(enabled);}注意它在async函数里所以抛出的是一个被拒绝的 Promise不是同步异常。这意味着// ❌ 抓不到try{Torch.switchState(1);}catch(e){}// ✅ 这样才抓得到awaitTorch.switchState(1).catch(econsole.log(e.message));这个细节在本轮验证中被实际确认过——参数守卫组的断言必须用await才捕获到TypeError。调用方如果按同步异常来写try/catch会漏掉所有参数错误。四、requestCameraPermission()为什么直接返回true这个方法最容易引起误解。它在 Android 上的语义是弹出系统相机授权框所以返回值表示用户授权了没有。到了鸿蒙实现直接写成asyncrequestCameraPermission():Promiseboolean{returntrue;}为什么可以这样写因为鸿蒙的手电筒 API 走的是CameraManager既不要求声明ohos.permission.CAMERA也不打开相机输入。既然没有权限可申请也就没有授权框可弹——直接返回true表示没有权限障碍你继续调switchState就行。这个true的含义要读准它表示它不表示不存在权限障碍设备一定有闪光灯可以继续调用switchState闪光灯能被点亮保留了上游接口的兼容性Android 上的授权弹窗行为也一致交付包 README 专门写了这个限定这一点做得很诚实。如果调用方把requestCameraPermission() true当成硬件可用的判据就会在无闪光灯设备上做出错误决策——正确的判据是switchState是否成功而不是权限方法是否返回true。五、接入宿主宿主工程是一个已经运行起来的 RNOH 示例应用。接入这个库要改四处文件改动package.json加react-native-torch: file:../react-native-torchmetro.config.jswatchFolders增加该库的绝对路径index.jsimport 测试页并注册harmony/entry/oh-package.json5手工加 HAR 依赖第四处是唯一需要留神的react-native-ohos/react-native-torch: file:../../node_modules/react-native-torch/harmony/torch.har自动链接工具不会写这一处。它会把package.json的依赖关系和 Metro 的模块解析处理好日志里也会报linked 9 libraries但模块级的oh-package.json5依赖需要手工补——漏了这一步编译期就会在 CMake / ohpm 阶段报找不到包。这一点交付包的 README 里也专门提醒了写成了一句很实在的话若宿主 CMake 的OH_MODULES_DIR指向entry/oh_modules还需在harmony/entry/oh-package.json5添加 HAR 依赖……不能据 link 成功省略这一步。这类工具链不会代劳的一步是接入环节最容易踩的坑link-harmony成功不代表接线完成得认准编译产物里到底有没有这个包。另一个刻意的选择没有改harmony/entry/src/main/module.json5。宿主的requestPermissions里只有requestPermissions: [ { name: ohos.permission.INTERNET }, { name: ohos.permission.VIBRATE } ]没有ohos.permission.CAMERA——保持原样因为本库不需要。这一点后来还成了一个有用的旁证见第七节。关于换测试页宿主用一个启动参数rnAppKey决定加载哪个测试页。这个参数只在冷启动时生效所以换页必须hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey TorchTestAppforce-stop不能省。少了它应用还活着新参数不会重新读取你会一直在看旧页面——而且页面长得没错很容易误判。六、构建与运行# 1. 装依赖npminstall# 2. 自动链接node_modules\.bin\react-native link-harmony# linked 9 libraries# 3. 装 HAR在 harmony/ 目录ohpminstall--all# 4. 出 bundlenpmrun dev# 5. 打 HAPhvigorw--modemodule-pproductdefault-pmoduleentrydefault assembleHap --no-daemon编译结果项数值首次assembleHap耗时6 分 39 秒HAP 变化80,714,316 →80,867,227 字节152,911 ≈149 KB原生注册日志#RNOH_ARK ... TM created: RCTTorch二次重编改了一条断言6 分 27 秒HAP 80,867,224 字节两个数量级上的观察149 KB vs 2,875 字节的 HARHAR 本身极小但会连带拉起相机相关的能力声明与依赖所以最终产物增量比 HAR 大两个数量级。别拿 HAR 体积估增量。编译耗时基本由 ABI / CMake 阶段决定跟这个库没关系加 19 行 ArkTS 不会让编译从 2 分钟变 6 分钟这 6 分钟是 RNOH 工程本身的量级做集成排期时要按这个数留时间。日志里的#RNOH_ARK标签说明这是ArkTS 侧 TurboModule不是纯 C TurboModule也不是 JSI。这个区别在排查时有用ArkTS 模块的问题看 ArkTS 日志纯 C 模块才有#RNOH_CPP的TurboModuleFactory.cpp记录。七、验证设计依赖硬件的库在模拟器上怎么验这是本文最重要的一节。7.1 问题灯亮不亮模拟器答不了模拟器Pura X Viewohos-x64没有闪光灯硬件。这意味着一件事switchState(true)之后灯是否真的亮了在模拟器上永远无法验证。如果验证方案写成调用switchState(true)断言返回true那么在这个环境下必然失败——但这个失败不是库的问题。反过来如果为了让测试通过而放宽断言又会遮盖真正该测的东西。所以验证设计的关键是换一个落点。7.2 换落点从硬件是否动作换到行为是否正确既然能不能亮验不了那就验能验的、而且同样重要的硬件不支持时它是明确失败还是返回true假装成功这个问题在模拟器上完全可以验证而且它比灯能不能亮更接近库的真实质量——因为调用方写降级逻辑时依赖的正是失败能被感知到这件事。一个在无硬件时静默返回true的库会让调用方以为灯亮了这比报错糟糕得多。于是把验证分成三层层能验吗内容A. 契约与类型✅ 完全可验两个方法存在、返回 Promise、权限方法解析为trueB. JS 侧参数守卫✅ 完全可验非布尔参数必须以TypeError拒绝6 种取值C. 硬件相关行为⚠️只记录不断言应当成功实测结局重点看是明确失败还是假装成功A、B 两层进通过率C 层如实记录。这样通过率是有意义的13 / 13同时不掩盖硬件未验证的事实。7.3 A 组契约与类型6 项全通过断言结果switchState是 function✅requestCameraPermission是 function✅requestCameraPermission()返回 Promise✅requestCameraPermission()解析为true✅switchState(true)返回 Promise✅switchState(false)返回 Promise✅这里有个细节让这组在无硬件时依然成立switchState是async函数无论内部成功还是抛错返回的一定是 Promise。所以返回 Promise这条断言与硬件无关可以放心验。7.4 B 组参数守卫7 项全通过非布尔参数必须以TypeError拒绝传入值结果undefined✅ TypeErrornull✅ TypeError0✅ TypeError1✅ TypeErrortrue✅ TypeError{}✅ TypeError非法参数之后合法调用不再被参数守卫拦住✅最后一条值得单独说因为它让我改过一次断言。初版我把它写成非法参数之后调用应当成功。结果在本机失败——因为无闪光灯时合法调用会以ERR_TORCH_UNAVAILABLE拒绝。那是正确的硬件行为不是参数问题但我的断言把两件事混在了一起。改判据后只检查结果里不再出现TypeError不管它最终是成功还是因硬件拒绝——这样断言只依赖参数守卫这一个契约与环境能力无关。教训测试断言不能隐含环境具备某能力这个前提。断言应该只依赖被测代码的契约不依赖环境能力。如果确实要断言环境就把它单独成组并如实记录而不是混进通过率里——否则通过率会随设备变化失去意义。7.5 C 组硬件相关行为如实记录四次调用的实测结果硬件switchState(true) 拒绝ERR_TORCH_UNAVAILABLE: flashlight mode is not supported 硬件switchState(false) 拒绝ERR_TORCH_UNAVAILABLE: flashlight mode is not supported 硬件switchState(true) 第二次 拒绝ERR_TORCH_UNAVAILABLE: flashlight mode is not supported 硬件switchState(false) 第二次 拒绝ERR_TORCH_UNAVAILABLE: flashlight mode is not supported 硬件两轮结果是否一致是 硬件四次调用是否都以 ERR_TORCH_UNAVAILABLE 拒绝是 硬件硬件不支持时是否返回 true 假装成功否明确失败后两条是页面从实测结果动态推导出来的不是写死的文案——这样它们才是断言而不是修饰。两个结论本机确实没有闪光灯—— 四次调用全部走到不支持分支两轮一致库的行为是对的——硬件不支持时明确拒绝没有返回true假装成功。第 2 条才是本轮真正验证到的东西而它恰好是模拟器能验、也最该验的那一条。7.6 两个独立旁证断言之外还找了两个不依赖测试页的旁证用来交叉验证确实没碰硬件。旁证一相机服务侧没有任何日志。在两次调用前后抓hilog没有相机服务的任何记录。这和代码路径完全一致isTorchSupported()检查失败就抛错了从未执行到setTorchMode所以系统侧没有动作可记。如果代码是先下发命令再检查结果就会看到系统侧有响应——日志的有无正好反证了检查发生在命令之前。旁证二没有CAMERA权限错误类型却是硬件不支持。宿主module.json5里没有声明ohos.permission.CAMERA而switchState返回的是ERR_TORCH_UNAVAILABLE: flashlight mode is not supported是不支持不是无权限。如果是权限问题报错应该是权限拒绝既然系统先给出了硬件不支持的判断就说明这条调用路径根本没走到权限校验——独立印证了鸿蒙手电 API 不需要相机权限这个结论。这两个旁证的价值在于它们不来自我的测试代码。一个来自系统日志一个来自工程配置都是外部事实。7.7 关于测试页本身测试页沿用了一个统一的断言夹具每条断言用record(组名, 描述, 期望, 实际)记录最后按组汇总通过 / 失败。硬件行为单独成组不进通过率。页面根节点包在SafeAreaView里避免被状态栏遮挡日志统一带[torch-test]前缀方便过滤。另外C 组的两个推导结论是运行时算出来的四次调用是否都以 ERR_TORCH_UNAVAILABLE 拒绝是 硬件不支持时是否返回 true 假装成功否明确失败这样做的好处是——换一台有闪光灯的设备跑这两行会自动变成否和不适用而不会继续打印是。写死的文案会骗人动态推导不会。八、真机验证哪些验过了哪些没验这一节必须写清楚因为这是本轮最容易被含糊过去的地方。8.1 已验证项证据两个方法的契约与类型A 组 6 条断言全通过JS 侧参数守卫6 种非布尔值B 组 7 条断言全通过原生模块成功注册hilog中TM created: RCTTorch模块是 ArkTS 实现日志标签为#RNOH_ARK硬件不支持时的失败模式C 组四次调用全部以ERR_TORCH_UNAVAILABLE明确拒绝未返回true假装成功两轮一致失败发生命令下发之前相机服务侧无任何hilog记录手电 API 不需要CAMERA权限宿主无该权限声明错误类型仍是硬件不支持而非无权限编译与产物assembleHap成功HAP 149 KB8.2 未验证本轮无法验证项为什么没验闪光灯是否真的会亮本机没有闪光灯硬件无法验证开启 / 关闭的实际硬件状态需要真机并需要看系统侧的手电状态事件相机被其他应用占用时的失败路径需要真实硬件与占用场景其他 ROM / 平板 / 无闪光灯真机的差异只有一台模拟器亮灯时的功耗与发热无硬件8.3 为什么要分开写因为这两类事的可信度完全不同。把已验证说成适配成功、功能正常是过度声明——灯到底亮不亮我没验过把未验证含糊成应该没问题会让下一位使用者踩坑反过来把硬件不支持时正确报错也归入未验证又低估了本轮的实际成果。准确的表述是软件层全通过硬件动作未验证但硬件不支持时的失败模式已确认为正确。九、已知限制模拟器没有闪光灯点亮无法验证。这是本轮最大的空白需要真机补充。requestCameraPermission()恒返回true是兼容桩而非硬件探测。它的返回值不能当作硬件可用的判据。调用方应改用switchState是否成功来判断。无权限声明的结论只在本轮环境成立。本轮验证了不声明CAMERA权限也能走到硬件检查但没有验证其他 ROM 上是否同样如此。未做跨平台对照。本轮只验证鸿蒙没有对比 Android / iOS 上同一接口的行为差异特别是requestCameraPermission的弹窗语义。ERR_TORCH_UNAVAILABLE是字符串前缀不是错误码。调用方靠自己匹配字符串来识别匹配时要考虑前缀而非全等否则错误文案微调就会破坏降级逻辑。单设备、单次运行。只在一台模拟器上跑过没有压力测试、没有并发调用场景、没有反复快速切换。未改动库代码也未发现缺陷。本轮唯一一次失败是我自己测试页的断言写错了隐含了硬件存在的前提修正后通过。十、常见问题Q1调用switchState(true)报ERR_TORCH_UNAVAILABLE是适配坏了吗不一定。先确认设备有没有闪光灯——模拟器上、平板上、很多无闪光灯设备上这个错误是正确行为。这个库的设计就是没有闪光灯就明确报错而不是静默成功。Q2requestCameraPermission()返回true了为什么switchState还是失败因为这两件事无关。true表示不存在权限障碍不表示设备有闪光灯。在鸿蒙上手电 API 根本不需要相机权限所以这个方法只是个兼容桩。Q3需要声明ohos.permission.CAMERA吗本库不需要本轮实测没有声明也能正常走到硬件检查。但如果你的应用自己还要做拍照、录像那当然要声明——那是另一条采集链路的事。Q4为什么try { Torch.switchState(1) } catch {}抓不到参数错误因为 JS 守卫在async函数里抛出的是被拒绝的 Promise。必须await或.catch()awaitTorch.switchState(1).catch(econsole.log(e.message));Q5link-harmony报链接成功编译还是找不到包检查harmony/entry/oh-package.json5有没有手工加上 HAR 依赖。自动链接工具不写模块级的这一处漏了就报找不到包——这不是链接失败是链接没覆盖到这里。Q6换了测试页代码界面还是旧的rnAppKey这类启动参数只在冷启动生效。先aa force-stop再aa start否则一直在看旧页面。Q7怎么在自己设备上确认闪光灯到底能不能用别信权限方法直接试try{awaitTorch.switchState(true);console.log(下发成功);// 注意命令被接受不代表灯一定亮了}catch(e){console.log(不可用,e.message);}注意日志里的措辞——即使返回成功也只表示命令被系统接受要确认灯真的亮了得看设备本身。这个区分在交付包的 README 里也写明了。Q8编译要 6 分多钟是不是这个库太重不是。这 6 分钟是 RNOH 工程本身 ABI / CMake 阶段的量级跟 19 行 ArkTS 无关。HAR 只有 2,875 字节但最终 HAP 增了 149 KB——别拿 HAR 体积估集成增量。小结react-native-torch是个小库两个 API、19 行原生实现、不需要任何权限。但它在方法论上给了一个很典型的题目——当库依赖的硬件在你手上不存在时验证该怎么做这次的做法是把验证分层。契约与参数守卫A、B完全可验进通过率硬件行为C只记录、不断言应当成功。换验证落点。既然灯亮不亮验不了就验**“硬件不支持时是否明确失败而不是返回true假装成功”**——这一条模拟器能验而且比能不能亮更接近库的质量。找不依赖测试代码的旁证。相机服务无日志证明检查发生在命令下发之前、无CAMERA权限却报硬件不支持证明这条路径不需要相机权限——两个都来自系统日志和工程配置不是我自己的断言。把已验证和未验证分开写。软件层全通过是真的硬件动作没验也是真的不能混成一句适配成功。另外两条从实践中来的经验断言不能隐含环境具备某能力。我初版把非法参数之后调用应当成功写成断言在有闪光灯的机器上会过在这台机器上必然失败。断言只该依赖被测代码的契约否则通过率会随设备漂移失去意义。requestCameraPermission()返回true不是硬件探针。它是权限语义上的没有障碍把两者混为一谈就会在无硬件设备上做出错误决策。还有一点关于交付包它自己在 README 里就写清了返回true不表示设备一定有闪光灯并且诚实声明了哪些场景是 mock 验证、哪些没做真机覆盖。一个交付包能主动划出自己的能力边界比它多写几个 API 有用得多——因为使用者正是靠这句话避免误判的。本篇用到的库库版本说明react-native-torch1.2.0手电筒 / 闪光灯控制本文适配对象。上游仓库ludo/react-native-torch接入方式// harmony/entry/oh-package.json5需手工添加 react-native-ohos/react-native-torch: file:../../node_modules/react-native-torch/harmony/torch.harimportTorchfromreact-native-torch;awaitTorch.requestCameraPermission();// 鸿蒙上恒为 true仅表无权限障碍try{awaitTorch.switchState(true);// 成功只代表命令被系统接受}catch(e){// 无闪光灯设备会抛 ERR_TORCH_UNAVAILABLEconsole.log(String(e));}awaitTorch.switchState(false);# 换页启动测试页force-stop 不能省换页参数只在冷启动生效hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey TorchTestApp验证环境项版本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——无闪光灯硬件宿主 HAP 产物entry-default-signed.hap80.87 MB本次增量构建assembleHap6 分 39 秒HAP 149 KB验证规模设备侧软件层 13 项断言全部通过硬件行为组 4 次调用如实记录全部ERR_TORCH_UNAVAILABLE欢迎加入 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
返回列表