资讯动态

React Native鸿蒙网络切换提示实现:从桥接到真机排障全解析

发布时间:2026/10/22 13:49:24 来源:尧图企业网站定制
前阵子我在给一个内部项目做跨平台迁移需求清单里有一条是“NetworkInfo网络切换提示”。项目本身是用React Native开发的目标平台是OpenHarmony迁移清单出来以后团队里很多人第一反应是RN不是主要支持安卓和iOS吗鸿蒙上能跑吗这个NetworkInfo功能会不会直接在原生侧重新写一遍实测下来React Native在OpenHarmony上不仅跑得通很多业务代码还能原样复用只是涉及系统能力的地方比如网络状态监听需要走一条带鸿蒙特色的桥接通道。这篇文章把我这次把NetworkInfo网络切换提示从鸿蒙原生搬到RN侧、再在真机上调到可用的全过程拆开讲包括架构选型、代码实现、权限配置、实测差异以及我为了“收不到切换提示”这个故障排查了整整两天半的完整链路。如果你是正在做RN上鸿蒙迁移、或者刚拿到一个“在鸿蒙App里监听Wi-Fi/蜂窝切换”需求的开发者这篇可以直接参考。我先说明一下这里说的NetworkInfo在RN生态里对应的就是react-native-community/netinfo这个库的能力不是安卓那边android.net.NetworkInfo类别搞混。整篇文章基于当时比较稳定的RNOH社区工程模板个别接口在不同版本里可能有细微差异但我下面写的思路和排障方向是通用的。1. 为什么迁移清单里第一个就是网络切换OpenHarmony与React Native的适配现状先解释一下背景免得有同学上来就懵。OpenHarmony这边的原生应用开发主要走ArkTS ArkUI这条线RN要跑上去依赖的是社区推动的React Native for OpenHarmony适配工程一般简称RNOH。它的思路是原生侧用ArkTS实现RN的运行时和渲染能力JS侧继续用React、React Native那套写业务。对业务层来说组件、状态管理、网络请求这些都能用但凡是需要系统服务的原生能力比如蓝牙、Wi-Fi状态、剪贴板、定位就必须有对应的原生模块桥接。1.1 不是所有RN第三方库都能在OpenHarmony上直接用这一点是迁移时最容易踩的认知坑。很多RN项目里用得很顺的库比如react-native-nfc、react-native-bluetooth、包括netinfo它们的原生实现是基于Android的BroadcastReceiver、iOS的Reachability或者CBCentralManager写的OpenHarmony上根本没有这些系统接口直接把npm包装进去JS侧代码能编译过但运行到调用原生方法时大概率静默失败或者直接报没法找到模块。netinfo这类库依赖系统网络状态的变化通知尤其需要一个正确的事件源。OpenHarmony上有自己的网络连接管理模块提供netAvailable、netLost、netCapabilitiesChange这类订阅事件也有getDefaultNet、getNetCapabilities这样的查询接口。问题是这些原生能力默认不暴露给RN的JS运行时必须写一个ArkTS原生模块把数据接出来再通过RN的事件通道丢给JS侧。这就是整个NetworkInfo网络切换提示功能最核心的工程点。1.2 网络状态感知为什么是业务刚需而不是“锦上添花”我之所以把这个功能放在迁移清单第一位是因为它在业务里不是一个可选项。项目里有个核心场景是在线视频播放用户在Wi-Fi环境下开启自动播放中途走到电梯间、切到移动数据网络这时候如果不提示就继续拉流用户流量账单会很难看投诉率直接上升。另一个场景是离线缓存队列断网时进入缓存暂停状态恢复网络后要自动重试上传前提是App能及时感知到网络类型从“无网”变成“有网”而且能区分是Wi-Fi还是蜂窝。在这些场景里NetworkInfo网络切换提示不是弹个Toast那么简单它要回答三个问题当前有没有网、网络类型是什么、上一次的网络类型是什么。只有把这三个信息连续起来才能判断“发生了切换”而不是“周期性的状态刷新”。这也是为什么很多人直接调接口查当前网络状态觉得没用——你要的是“变化事件”不是“当前快照”。2. 搭建RNOH工程时的三件小事开发环境、依赖包与初始化配置在写任何网络监听代码之前先把工程跑通。这一节的三个细节看起来基础但我见过太多人卡在这里而且报错信息都藏在构建阶段不在运行时。2.1 开发环境与版本锁定别手滑升级我用的工具链是这样的DevEco Studio做原生壳工程OpenHarmony SDK跟着DevEco内置版本走RN部分基于社区维护的RNOH模板工程初始化里面预置了RN runtime、原生渲染器和基本的TurboModule框架。这里一定要强调版本锁死。RNOH的适配进度和RN官方版本不是完全同步的社区的适配包、包括后面要装的netinfo补丁包都是针对特定RN小版本做的验证。我在项目里用package.json把react-native版本锁定到精确版本不加^或~同时把RNOH模板的版本也固定住。升级一个补丁版本导致原生构建不过或者JS侧接口消失这种事在社区反馈里太常见了。锁定版本之后npm install就不应该再产生任何依赖漂移。2.2 安装适配OpenHarmony的NetInfo包并检查原生绑定官方版的react-native-community/netinfo在OpenHarmony上是不会工作的因为有原生代码依赖Android系统接口。社区提供了一版适配包名字类似react-native-oh-tpl/netinfo它内部换成调用OpenHarmony的网络连接管理能力对外API尽量保持一致。安装结束后最好做一次原生构建验证找一个简单页面调用NetInfo.fetch()如果能拿到网络状态再继续做事件监听。如果这一步就返回undefined或者抛异常多半是原生模块没注册成功。RNOH里原生模块的注册方式和Android很像要在原生侧把模块实例注册进RNInstance装完包之后如果构建产物是HAR或HSP结构还要确认有没有被主工程正确依赖。我第一次装完就是这个包在oh_modules里看得见但构建产物没包含排查很久才发现是依赖路径配置问题。2.3 module.json5里的权限声明漏了就是静默失败鸿蒙应用声明权限的位置是entry/src/main/module.json5网络状态查询和订阅需要显式声明ohos.permission.GET_NETWORK_INFO。这里有个很坑的点不声明权限时不是每次调用都报错而是在部分系统版本上订阅事件静默不生效NetInfo.fetch()也可能返回一个永远固定的旧状态。尤其是你从Wi-Fi切到蜂窝网络时原生侧根本收不到netLost事件JS侧自然就一直停留在“已连接”状态。权限配置如下{ module: { name: entry, requestPermissions: [ { name: ohos.permission.GET_NETWORK_INFO } ] } }权限名称不要凭记忆敲最好在SDK的权限定义文件里复制大小写错一个字符构建不报错但运行时权限就是拿不到。我有一次把GET_NETWORK_INFO写成了GET_NETWORK_STATUS折腾了一晚上最后对着源码里的权限常量才发现。只要这三件小事做到位RN和OpenHarmony原生之间的网络通道就算打通了接下来才是真正的功能实现。3. NetworkInfo网络切换提示的实现方案对比与核心代码JS侧监听为主还是双端事件通道NetInfo在RN上最常见的用法就是useNetInfo监听但放到OpenHarmony上不同场景的表现差异很大。我梳理了三个可选方案并且实际验证了它们的边界。3.1 三种方案对比轮询、自带事件、自定义native事件方案优点缺点适用场景纯JS轮询定时调fetch实现最简单不依赖额外原生适配耗电、延迟高、拿不到切换瞬间的过渡状态低频非关键场景比如设置页的“当前网络状态”展示RNOH适配版NetInfo自带事件监听API兼容好代码侵入小大多数正常切换能监听个别系统事件飞行模式恢复、多网卡切换有延迟或漏报通用场景够用了但不要指望它覆盖100%切换路径RNOH自定义原生事件通道可控性最强能拿到鸿蒙网络模块的第一手事件需要写ArkTS原生模块和事件桥接工作量上去了项目里对“Wi-Fi切蜂窝”提示有强诉求、甚至是资费敏感场景时强烈建议做这层我最后采用的是方案二为主、方案三兜底的混合结构。日常的Wi-Fi/蜂窝切换提示交给适配版NetInfo如果业务方后续要求在飞行模式恢复、热点断开这类极端场景也必须有提示方案三的事件通道随时可以接上。3.2 RN侧监听与切换提示的核心逻辑业务需求是从Wi-Fi切换到蜂窝网络时弹一个明确提示“您已切换到移动数据网络继续播放将消耗流量”。RN侧的代码我封装成一个自定义hookimport { useEffect, useRef } from react; import NetInfo, { NetInfoState } from react-native-community/netinfo; /** * 监听网络类型连续变化只在满足业务条件时回调 * onSwitch网络类型确实发生变化 * onUseCellular切换到了蜂窝网络 */ export function useNetworkSwitchMonitor( onSwitch?: (from: string, to: string) void, onUseCellular?: () void, ) { // 用ref保存上一次状态避免在依赖数组里反复创建定时器 const lastStateRef useRefNetInfoState | null(null); useEffect(() { const unsubscribe NetInfo.addEventListener((state) { const prev lastStateRef.current; lastStateRef.current state; if (!prev) return; // 首次回调只做记录不触发提示 const prevType prev.type; const currType state.type; // 网络类型没变化但连接状态变化比如断网/恢复也归为一次切换 if (prevType currType prev.isConnected state.isConnected) { return; } // 过滤掉unknown和none这种过渡状态避免在切换瞬间连续弹窗 if (currType none || currType unknown) { return; } if (prevType ! currType) { onSwitch?.(prevType, currType); } // 这里专门处理 非蜂窝 - 蜂窝 的场景比如从Wi-Fi到蜂窝或从无网到蜂窝 if (currType cellular) { onUseCellular?.(); } }); return () { unsubscribe(); }; }, [onSwitch, onUseCellular]); } // 页面里这样用 function PlayerScreen() { useNetworkSwitchMonitor( (from, to) { // 打印一个结构化日志方便在真机上看切换链路 console.log([NetworkInfo] network changed: ${from} - ${to}); }, () { showNetworkChangeModal(当前使用移动数据网络继续播放将消耗流量); }, ); return Player /; }几个细节值得说明。第一一定要记录上一次的状态并且在回调里先过滤首次事件因为NetInfo在subscribe时会立刻回推一次当前状态如果不做prev判断页面一进来就会误判成一次“切换”。第二切换瞬间经常出现type为unknown或者none的过渡态如果这个状态也弹窗用户会在Wi-Fi和蜂窝之间切换时看到两三次提示非常烦人所以我在代码里直接跳过了none和unknown只等稳定网络类型出现再提示。第三如果业务要求“断网时也提示”那判断条件要从类型变化扩张成连接状态变化这个用isConnected字段判断代码里我保留了注释位置。3.3 鸿蒙侧的网络能力采集与事件回传如果你需要方案三的兜底能力就必须在ArkTS原生侧写一个模块。我这里的做法是原生侧订阅鸿蒙网络连接模块的三种事件组装成统一结构后通过RNOH的事件发射器发送到JS侧。// ArkTS侧NativeModule内部的关键逻辑 import { connection } from kit.NetworkKit; import { turboModule } from ohos.TurboModule; // netHandle为空时表示没有默认网络 function getNetworkState(): NetworkChangePayload { const netHandle connection.getDefaultNetSync(); if (!netHandle) { return { type: none, isConnected: false, timestamp: Date.now() }; } const caps connection.getNetCapabilitiesSync(netHandle); if (caps caps.bearerTypes.length 0) { const bearer caps.bearerTypes[0]; return { type: bearer connection.NetBearType.BEARER_WIFI ? wifi : bearer connection.NetBearType.BEARER_CELLULAR ? cellular : unknown, isConnected: true, timestamp: Date.now(), }; } return { type: unknown, isConnected: false, timestamp: Date.now() }; } // 事件订阅把鸿蒙系统事件转成RN侧事件 connection.on(netAvailable, () { const payload getNetworkState(); nativeEventEmitter.emit(NetworkInfoChange, payload); }); connection.on(netLost, () { const payload getNetworkState(); nativeEventEmitter.emit(NetworkInfoChange, payload); }); connection.on(netCapabilitiesChange, () { const payload getNetworkState(); nativeEventEmitter.emit(NetworkInfoChange, payload); });JS侧如果要用这个事件只需要import { DeviceEventEmitter, NativeEventEmitter, NativeModules } from react-native; const networkNativeEmitter new NativeEventEmitter(NativeModules.NetworkInfoNativeModule); networkNativeEmitter.addListener(NetworkInfoChange, (payload: any) { // 与NetInfo事件相同逻辑处理切换提示 console.log([NetworkInfo] native event: , payload); });这里有个重要选择鸿蒙侧到底该用NetInfo这种封装库还是自己写原生事件。我的判断是如果业务只需要Wi-Fi/蜂窝切换提示而且项目本身已经集成了适配版NetInfo那就先别造轮子。原生模块的维护成本主要在版本升级——RNOH的TurboModule接口换版本的时候原生侧注册代码很可能也要跟着改多一个模块就多一分维护量。但如果你需要拿到netCapabilitiesChange这种细粒度事件或者要同时感知多个网络接口的状态那就必须走方案三因为NetInfo封装后你拿不到鸿蒙原生事件的原始细节。4. 真机实测3个影响提示体验的隐形因素代码写完后我在两台真机上做了长时间测试一台平板、一台手机覆盖Wi-Fi断开、Wi-Fi切蜂窝、飞行模式恢复、热点断开这些场景。有些问题从代码逻辑上根本想不到只有真机切换才暴露得出来。4.1 切换时延与瞬间过渡类型先说时延。从物理上断开Wi-Fi到JS侧收到“网络类型变化”事件中间隔着系统网络模块的状态刷新、ArkTS原生侧的事件回调、RNOH的桥接序列化我实测下来通常在1.5秒到3秒之间。这个延迟对提示类功能是可以接受的但不代表可以掉以轻心因为在这1.5秒到3秒的窗口里鸿蒙侧网络状态可能会经历一个从“wifi”到“unknown”再到“cellular”的过程。这就直接影响了JS侧代码怎么写。我在前面的hook里专门过滤了unknown和none避免在过渡状态时连续触发。如果不过滤你会在切换瞬间收到2到3次回调业务层以为是网络又断又连实际上只是系统在重新评估网络质量。另外getNetCapabilities返回的bearerTypes是个数组理论上一个网络可能同时具备多种承载类型。我在代码里只取了第一个在实际测试中没有发现问题但如果你做多网络同时在线场景建议还是遍历数组判断是否包含Wi-Fi再决定提示策略。4.2 生命周期与后台恢复切走再切回来提示事件丢了这个问题是测试快结束时发现的。App退到后台再回来网络从Wi-Fi切到蜂窝结果什么都没弹。第一反应以为是监听回调被回收了查了一圈发现不是。真相是App退到后台之后鸿蒙系统为了省电会暂停JS线程的调度网络模块的on事件在系统侧是有触发的原生侧也能收到但事件发到JS侧时JS端没有及时消费部分事件在桥接队列里被后置处理等App回前台时队列里的网络事件已经被标记为过期或者JS侧的监听器被系统回收重建了。解决方案是在App生命周期恢复到active状态时主动拉一次当前网络状态并和上一次记录的状态比对如果发现网络类型已经变化就补发提示import { AppState } from react-native; AppState.addEventListener(change, (state) { if (state active) { // 主动获取一次网络状态 NetInfo.fetch().then((currentState) { const prev lastStateRef.current; if (prev prev.type ! currentState.type) { // 补发一次切换提示 handleNetworkSwitch(prev.type, currentState.type); } }); } });这样就算后台期间事件丢了用户回前台时也能看到正确的提示损失最多是展示时延不会漏报。4.3 电量与频繁回调监听全部网络事件是个陷阱我第一次做原生侧模块时图省事把鸿蒙网络模块里所有能订阅的事件都挂上了netAvailable、netLost、netCapabilitiesChange、netConnectionPropertiesChange。结果真机测试一小时手机温度明显上升排查之后发现是netConnectionPropertiesChange执行频率太高这个事件在网络状态稳定时也会周期性触发每次都走一遍桥接导致JS侧压力剧增。后来调整成只订阅netAvailable、netLost和netCapabilitiesChange三个事件并在JS侧对同一秒内的重复回调做了合并处理温度恢复正常。如果你要做的只是“切换提示”建议最多监听这三个事件就够了不要再往上加。5. 排障实录收不到网络切换提示的完整排查链路如果你的代码写完了但真机上就是不出提示下面是三个我遇到过、且有完整确认过程的故障每个都有根因和修复链路建议按顺序走一遍。5.1 现象一onStateChange回调一直不触发先别怀疑代码先确认系统侧有没有发出事件。做法很简单用DevEco Studio的日志窗口在鸿蒙原生侧打点手动开关飞行模式看原生侧有没有收到netLost、netAvailable的事件日志。原生侧没有日志说明问题在权限或系统网络模块本身原生侧有日志但JS侧没有问题在桥接或权限。我在项目里遇到过的情况是权限没加。前面说过的ohos.permission.GET_NETWORK_INFO漏了结果onStateChange一次都不触发。加了权限、重新构建后问题立刻消失。这个坑之所以隐蔽是因为很多现有工程模板里默认没这个权限而NetInfo在Android上不查这个权限也照样能用迁移过来之后就忘了查鸿蒙侧权限。第二个常见根因是事件名大小写不匹配。原生侧emit(NetworkInfoChange)JS侧addListener(networkInfoChange)这种错误在编译期不报错运行时也静默只能通过一遍一遍查名字来发现。建议JS侧和ArkTS侧的事件名统一抽成常量最好不要手敲。5.2 现象二断网检测正确恢复网络不提示这个现象更让人头疼。从Wi-Fi切到无网提示弹出来了但把Wi-Fi接回来没有提示。反复测试发现不是每次都有十次里有两三次会漏。后来在原生侧打点发现netAvailable事件在系统侧触发了但触发时getDefaultNet返回的handles可能还没完全稳定下来我拿到的caps是undefined于是组装出来的状态是none被我司JS侧过滤掉了。也就是说事件没丢是我先生成数据的时候拿了一个过渡态然后JS侧又过滤了它两头一叠加就漏报了。修复思路是给原生侧加一个重试机制收到netAvailable后如果发现拿不到稳定网络状态延迟200毫秒再查一次最多查三次。200毫秒这个数字是可以按需要调整的但别太大否则用户感知的提示时延会明显增长。// ArkTS侧netAvailable处理 connection.on(netAvailable, () { retryGetNetworkState(0); }); function retryGetNetworkState(attempt: number) { const payload getNetworkState(); if (payload.type none attempt 3) { setTimeout(() retryGetNetworkState(attempt 1), 200); return; } nativeEventEmitter.emit(NetworkInfoChange, payload); }5.3 现象三真机偶发崩溃堆栈指向原生侧网络模块这个问题出现频率不高但一旦出现就影响线上体验。崩溃堆栈指向ArkTS原生侧的setTimeout回调原因是我在retry逻辑里用了setTimeout但网络状态变化时App可能已经退到后台甚至关掉了页面原生侧模块持有的JS上下文已经失效这时再往JS侧emit事件就会触发非法状态。修复方式是在原生模块里增加生命周期清理页面卸载或App进入后台时取消所有尚未执行的retry定时器并把已经注册的connection事件全部移除掉。JS侧也要在unmount时调用removeEventListener不要依赖App进程销毁时的自动清理。我还额外做了一件事在进入后台超过一定时长后主动把原生侧的网络订阅停掉等App回到前台时再重新订阅。这既减少了崩溃可能性也顺便解决了前面提到的耗电问题。这次做完以后我对RN在OpenHarmony上的落地信心增强了不少。网络状态感知是整个系统能力里相对底层、但业务依赖度又很高的一块能把NetworkInfo切换提示在这个平台上彻底跑通说明RNOH的原生桥接、事件通道和生命周期管理这几个核心链路都是可靠的。如果拿到这个改造需求我给你的建议是权限声明第一时间加好JS侧一定要做上次状态比对原生侧的事件订阅越精简越好。先把这个小功能打通后面再摸别的原生能力就有了一套自己熟悉的排查套路了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑