1. 为什么要做“网络切换提示”这件事
先说个实际场景。我在地铁上刷新闻,电梯里回工作消息,高铁过隧道时看视频——几乎每天都会遇到网络状态突然变化的情况。对普通用户来说,信号从5G掉到4G可能只是右上角图标变一下,但对你正在用的App来说,这很可能意味着请求超时、视频卡顿、上传失败,甚至数据错乱。
我自己在做某跨平台系统的时候,就吃过这个亏。当时用React Native适配OpenHarmony设备,业务里有个依赖实时网络状态的功能模块,用户抱怨在切换Wi-Fi和蜂窝网络时,App表现很不稳定。排查了一圈,问题不是出在React Native层,而是网络状态变化的监听没有做好——系统在切换网络时给了信号,业务层没接住,导致回调里拿到的还是旧网络信息。
这就是NetworkInfo网络切换提示要做的事:通过框架提供的网络能力接口,实时感知网络状态的变更事件,把“当前网络类型”“网络是否可用”“是否发生切换”这些关键信息准确传达给业务层,并在合适的时机给用户一个明确的提示。这篇博客把我在React Native + OpenHarmony组合下实现这一功能的完整过程和踩坑记录写下来,希望能帮到正在做类似功能的开发者。
适合谁看?如果你在用React Native开发OpenHarmony应用,或者正在为App适配鸿蒙生态,又或者只是对跨端框架如何调用系统底层网络能力感兴趣,这篇文章都能给你一些参考。我不会只贴代码,更重要的是把背后的原理和“为什么这么做”讲清楚。
2. 项目整体设计与技术方案选型
2.1 核心需求拆解
在动工之前,我先把需求拆成了几个清晰的点:
- 监听网络状态变化,包括Wi-Fi、蜂窝网络(4G/5G)、以太网等类型的切换
- 获取当前网络类型和网络可用性状态
- 当网络状态发生切换时,在App界面上给出提示
- 在网络恢复或断开时,业务模块能够收到通知并做出响应
这些需求听起来简单,但真正落地时有一个核心难点:React Native层是JavaScript环境,要拿到OpenHarmony系统底层的网络状态变更事件,中间必须经过桥接层。选什么方案、怎么设计桥接结构,直接决定了这个功能的稳定性和开发效率。
2.2 方案选型:为什么走原生桥接,而不是轮询
刚开始我考虑过一个偷懒的方案:写一个定时器,每隔几秒调用接口查询一次当前网络状态,如果发生变化就触发提示。这个方案实现起来确实简单,能跑,但有几个明显问题:
- 实时性差。从网络切换发生到业务层感知到,中间可能间隔好几秒,用户看到提示时网络早就切完了。
- 消耗额外资源。定时器需要周期性唤醒JS线程,对于强调续航和流畅度的移动端场景来说并不划算。
- 事件丢失风险高。假如网络在两次轮询之间做了两次切换,轮询只能感知到最后一次状态,中间的变化在业务层丢了。
所以最终采用了“事件驱动”的方案,通过OpenHarmony系统提供的网络状态监听接口来注册回调,当网络状态真正变化时,系统主动通知到应用层,再由原生桥接把事件转发给React Native的JS侧。这种方式不但实时性好,也把资源消耗降到了最低。
2.3 为什么在RN + OpenHarmony场景下要格外重视这一块
React Native本身提供了NetInfo这个社区库来处理网络状态,但在OpenHarmony平台上,社区库的原生实现通常只针对Android和iOS的API做了适配,无法直接使用。适配OpenHarmony时,最稳妥的办法是自己封装原生模块,利用OpenHarmony的Ability框架和系统API实现网络状态监听,再通过RN的TurboModule或传统Bridge机制暴露给JS层。
我选的方案是使用OpenHarmony的Native API(即NDK能力)配合Kotlin形式的本地模块实现。严格说,RN在OpenHarmony上的运行时结构跟Android类似,有独立的UI管理器和模块调用机制,自定义原生模块的写法也有自己的规范。我们在项目里是通过RNOH(React Native OpenHarmony)这个适配层来完成桥梁调度的。
2.4 整体架构图景
整个功能的调用链路大致是这样:
JS业务层 -> RN模块调用 -> 原生桥接模块 -> OpenHarmony网络子系统 -> 系统网络回调 -> 原生桥接回调 -> JS事件分发 -> UI提示这一条链路上的每一环都必须可靠。网络状态监听一旦断开,整个功能就成空壳了;桥接层如果处理不好生命周期,又容易造成内存泄漏或者事件重复注册。这些都是我在实际开发中重点注意的地方。
3. 核心细节解析:NetworkInfo的能力与接口
3.1 OpenHarmony侧的网络状态能力
OpenHarmony的系统API里,网络管理模块提供了获取网络状态、监听网络变化等能力。常用的几个关键接口有:
- 获取当前默认网络(即设备当前正在使用的网络连接),判断它属于什么类型
- 注册网络状态变更监听,当网络连接状态改变时触发回调
- 获取特定网络的接入方式,例如是Wi-Fi还是蜂窝网络,蜂窝网络下还能细分是4G还是5G
这里有一个要注意的点:OpenHarmony返回的网络类型枚举和Android不完全一样,不能直接把Android的代码搬过来用。我在适配时先做了一层映射,把OpenHarmony返回的类型统一转换成我们自己业务层的类型定义,这样上层JS代码就不用关心底层平台差异了。
3.2 事件监听与回调机制
使用系统网络管理模块的能力时,最关键的是正确注册和注销监听。注册回调后,系统在网络状态变更时会调用回调函数,回调里携带最新的网络状态信息。我在回调里做的事:
- 重新获取当前网络类型
- 重新获取网络可用状态
- 构造一个网络状态对象
- 把对象通过桥接层传给JS侧
需要特别注意的是回调线程。系统回调通常不在UI线程执行,如果你直接在回调里操作界面或者做重量级操作,会影响性能,严重时会引起应用无响应。我的做法是在回调里只做状态收集和事件转发,不涉及任何UI操作。
3.3 JS侧的数据结构设计
JS侧我定义了一个统一的数据结构来表示网络状态,其中关键字段有:
- isConnected:布尔值,表示网络是否可用
- isInternetReachable:布尔值,表示是否能够访问公网
- type:字符串,表示网络类型,如“wifi”“cellular”“ethernet”“none”
- details:对象,存放网络细节,比如蜂窝网络的信号强度、运营商信息等
之所以要区分isConnected和isInternetReachable,是有讲究的。前者表示设备是否连上了一个网络,但连上不代表能通网——比如连了一个需要登录的公共Wi-Fi,信号满格但实际无法访问互联网。如果只判断前者,App会误以为网络一切正常,结果请求一直超时。这个坑我踩过,后面在“常见问题”章节里详细讲。
3.4 为什么需要“网络切换提示”而不是单纯的状态展示
很多开发者会问:我直接获取当前网络状态,展示在界面上不就行了,为什么要费劲做“切换提示”?
区别在于:状态展示是“点”,切换提示是“过程”。状态展示只能告诉你“现在网络是Wi-Fi”,但你不知道用户上一秒是不是在网络不通的环境下。而切换提示能告诉用户和业务层,“网络刚刚从Wi-Fi切换到了蜂窝网络”,并且给出对应处理。
举个例子:用户在用Wi-Fi看视频,网速很好。突然走出信号范围,Wi-Fi断开,系统自动切换到蜂窝网络。如果没有切换提示,视频App可能继续按原来的网络状况发起请求,结果全部失败。有了切换提示,App就能及时感知变化,暂停自动加载、提示用户当前使用的是移动数据流量,或者重新调整清晰度策略。这就是这个功能真正的价值。
4. 实操过程:从零实现NetworkInfo网络切换提示
4.1 环境准备与工程结构
我用的开发环境是某基于OpenHarmony的IDE环境,因为涉及React Native适配层,我建议大家先确认自己本地的环境能正常编译和运行一个空的React Native OpenHarmony工程。工程结构采用标准的RN结构:
项目根目录 ├── entry/src/main/ │ ├── ets/ │ ├── cpp/ │ └── resources/ ├── node_modules/ ├── App.tsx ├── package.json └── ...其中原生模块代码放在entry模块里,JS业务代码放在根目录的App.tsx和相应业务目录中。RNOH框架有特定的模块注册和导出流程,下面分步骤说明。
4.2 原生模块:注册网络状态监听
原生侧我写了一个Module类,继承RNOH提供的BaseNativeModule。它对外暴露的接口有两个:一个是查询当前网络状态(一次性返回),另一个是开始/停止监听网络变化(对应JS侧的addEventListener和removeEventListener)。
关键代码如下(示意):
import { NetworkManager } from '某OpenHarmony系统包'; export class NetworkInfoModule extends BaseNativeModule { // 获取当前网络状态 getCurrentNetworkStatus(): NetworkStatus { const connectionManager = NetworkManager.getConnectionManager(); const networkInfo = connectionManager.getCurrentNetworkInfo(); return { isConnected: networkInfo.isConnected, isInternetReachable: networkInfo.isInternetReachable, type: mapNetworkType(networkInfo.type), details: { networkType: networkInfo.type, }, }; } // 注册监听 startListening(): void { this.connectionManager = NetworkManager.getConnectionManager(); this.callback = (networkInfo) => { const status = { isConnected: networkInfo.isConnected, isInternetReachable: networkInfo.isInternetReachable, type: mapNetworkType(networkInfo.type), }; this.emitEvent('networkStatusChange', status); }; this.connectionManager.registerNetworkStatusCallback(this.callback); } // 注销监听 stopListening(): void { if (this.connectionManager && this.callback) { this.connectionManager.unregisterNetworkStatusCallback(this.callback); } } }这里注意到我用了某OpenHarmony系统包来表示系统API包名,不同版本API对应关系不太一样,建议以开发环境里实际导入的SDK版本为准。映射函数mapNetworkType负责把系统返回的枚举转成业务层类型。
4.3 桥接模块注册
在RNOH架构里,自定义模块需要通过特定的注册机制暴露给JS。需要修改模块加载器配置,在运行时把NetworkInfoModule实例化并交给RN的运行时管理。具体牵涉到模块参数对象、包名注册等细节。
因为RNOH的版本迭代比较快,这里建议读者去参考当前使用的RNOH版本对应的官方示例工程,找到模块加载器配置的地方,照着加一行注册代码即可。这一块没有太多可以偷懒的地方,不同版本API变化较大,必须按版本要求来。
4.4 JS侧封装
在JS侧,我封装了一个useNetworkInfo的Hook,把它做成React组件直接可用的形式:
import { NativeModules, NativeEventEmitter } from 'react-native'; const { NetworkInfoModule } = NativeModules; const networkInfoEmitter = new NativeEventEmitter(NetworkInfoModule); export function useNetworkInfo() { const [networkStatus, setNetworkStatus] = useState(null); useEffect(() => { // 先主动查一次当前状态,避免等待系统回调 NetworkInfoModule.getCurrentNetworkStatus().then(setNetworkStatus); // 注册监听 const subscription = networkInfoEmitter.addListener( 'networkStatusChange', setNetworkStatus ); NetworkInfoModule.startListening(); return () => { subscription.remove(); NetworkInfoModule.stopListening(); }; }, []); return networkStatus; }关键点:在useEffect里先主动拉取一次状态,是为了不让UI等待系统回调——系统回调只有在网络状态发生变化时才会触发,而用户打开应用时,网络状态可能一直没有变化,这样UI就永远拿不到初始状态了。这个细节在实现时必须考虑到。
4.5 UI提示的实现策略
拿到网络状态后,UI层怎么提示也要讲究。我的实现逻辑是:
- 监听网络状态变化事件
- 缓存上一次的状态
- 当新状态与旧状态不相同时,弹出提示
- 提示的内容根据具体切换类型决定
比如Wi-Fi切换到蜂窝网络时,提示内容会变成“当前使用移动数据”,并附上一个确认按钮;网络完全断开时,提示变成“网络不可用”;网络恢复时,提示“网络已恢复”。
Toast弹窗我用的是RN常用的提示组件,但要注意调用时机。如果你在事件回调里直接同步弹窗,Android和OpenHarmony上可能没问题,但在某些页面上会出现弹窗时机过早导致UI还没准备好。稳妥的做法是配合一个消息总线,或者使用全局模态组件来控制提示的展示。
4.6 完整链路联调
写完模块后,我做了几个场景的联调测试:
- 打开App,确认初始网络状态正常获取
- 关闭Wi-Fi,观察是否触发蜂窝网络切换提示
- 开启飞行模式,观察是否触发网络断开提示
- 关闭飞行模式,观察是否触发网络恢复提示
- 反复切换Wi-Fi和蜂窝网络,确认事件不重复、不丢失
其中第5项最容易出问题。因为系统事件回调的频率可能很高,一个切换动作可能触发多次回调。如果不做去重处理,用户会看到连续弹出好几个一模一样的提示,体验很差。所以我在JS侧加了一层去重逻辑:只处理网络类型或可用性真正发生变化的情况。
5. 常见问题与排查技巧实录
5.1 网络状态提示不触发
我在开发初期遇到过一个问题:JS侧注册了监听,但网络一切换,提示就是不冒出来。排查了大半天,最后发现是原生模块的监听回调虽然在执行,但事件没有正确发到JS侧。
排查思路如下:
- 先在原生侧打日志,确认系统回调是否触发
- 再在桥接层打日志,确认事件是否发出
- 最后在JS侧打日志,确认监听是否注册成功
最终定位到问题是模块实例没有设置事件处理器。在RNOH的模块基类里,有专门的事件发送器,如果你不在初始化时把它接好,事件发出去也是空放。改成正确的方式后,提示马上就出来了。
这里分享一下经验:遇到这类“事件完全没反应”的问题,别急着怀疑系统能力,先从桥接链路逐层排查。日志打得越细,定位越快。
5.2 从Wi-Fi切换到移动网络时的重复提示
这个我在前面提过。系统在Wi-Fi断开和蜂窝网络建立之间,会有一个短暂的中间状态。回调可能先是“Wi-Fi断开”,然后是“蜂窝网络建立”,然后系统可能又因为某种原因短暂断开再重建,结果用户看到了三四个提示。
解决办法是在JS侧做一个状态机。只维护一个“当前有效网络状态”,每次收到新状态时,才决定是否需要更新UI。如果新状态和当前状态类型相同且可用性状态相同,直接忽略。这样用户最多看到一次有效的切换提示。
5.3 isConnected与isInternetReachable:两个坑要分清
有一个真实案例:某开发者做网络监听,用的字段是isConnected来判断网络是否存在。结果用户连着公司Wi-Fi但Wi-Fi需要网页认证,App一直显示网络正常,请求却全部超时。后来改成同时判断isInternetReachable,问题才解决。
这个坑的根源在于,连接上网络不等于能访问互联网。公共Wi-Fi、企业认证网络、甚至某些运营商的热点,都存在“能连接但不能上网”的情况。如果你的业务对网络可用性要求高(比如同步数据、播放视频),建议同时关注这两个字段。对于需要实时联网的业务,isInternetReachable比isConnected更关键。
5.4 内存泄漏:监听未注销
React Native应用在页面销毁时,如果忘了注销网络监听,会导致内存泄漏。尤其是单页应用反复切换页面,监听不断增加,最终应用会变卡甚至崩溃。
解决方案很简单:在useEffect的清理回调里,一定记得调用stopListening。另外原生侧也要做双重保障,在Ability销毁时主动释放资源,避免用户杀掉页面但原生模块还挂着回调。
我在项目里还加了一个检测机制:每次注册前先查看是否已有监听,如果有,先注销旧的再注册新的,避免重复注册。
5.5 RNOH版本兼容问题
最后一个是很多新手会忽略的:RNOH的版本迭代很快,API会变。同一个代码在不同版本上编译报错很常见。我自己就遇到过接口改名、包名调整、模块基类变化等问题。
建议项目固定RNOH版本,升级时单独验证网络模块的兼容性。不要因为主框架升级了,就直接把原生模块代码原封不动搬过去,编译能过不代表运行没问题。
6. 网络切换提示的业务扩展思路
6.1 结合网络类型调整内容加载策略
既然拿到了实时网络类型,业务层可以做很多事。视频类App可以在Wi-Fi下自动加载高清画质,在蜂窝网络下降级到流畅画质;下载类App可以在网络切换时暂停任务,提示用户确认。这些都是提升体验的实用功能,和NetworkInfo配合起来非常自然。
6.2 与全局状态管理联动
我的做法是把NetworkInfo的状态接入全局状态管理(比如常用的Redux或Zustand)。这样任何组件都可以直接读取网络状态,在渲染层面根据网络状态做出不同的展示,比如断开网络时展示离线页面、弱网时展示加载失败的重试按钮。
6.3 弱网模拟与测试
联调阶段如果你找不到真实弱网环境,可以用开发者工具的网络限速功能来模拟。把上行和下行带宽调低,观察App行为是否符合预期。不过我建议还是要在真实环境下做几轮测试,因为模拟环境和真实信号环境的差异还是很大的——尤其对于网络切换这个场景,速度快慢、信号强弱、基站切换算法都会影响用户体验。
我常用的测试方法:手机连接Wi-Fi,然后带着手机快速离开Wi-Fi覆盖区,观察蜂窝网络切换时App的表现。这个方法简单粗暴,但比模拟器靠谱得多。
6.4 从提示到自动处理:一个进阶想法
网络切换提示不只是给用户看的,更应该是给系统自我的调节信号。我在项目里做过一个增强逻辑:
- 检测到网络从Wi-Fi切换到蜂窝网络时,自动暂停非核心的后台上传任务
- 检测到网络断开时,自动保存草稿并进入离线模式
- 检测到网络恢复时,自动重试之前失败请求
这一套逻辑下来,用户体验会好很多。用户不需要理解网络切换的具体细节,但App替他处理好了所有网络变化带来的问题。
7. 实战心得与避坑总结
做完这个功能,我最大的体会是:在跨端框架下做系统能力适配,最难的往往不是功能本身的逻辑,而是“桥接层”的细节。系统API写得再好,桥接层处理不好,功能照样不可用。
我把这次实践中比较有价值的经验整理成清单:
- 优先选事件驱动,不要用轮询。实时性和资源消耗差距很明显。
- 桥接层要区分“业务无关的状态收集”和“业务相关的事件分发”,把职责划分清楚。
- 如果能力允许,给原生模块增加自动重连逻辑,防止系统网络服务重建导致监听失效。
- 统一网络类型枚举映射。业务层不要依赖具体平台类型定义,不然以后适配其他平台又要大改。
- 预留扩展字段。刚开始只做了类型和连通性,后面需要加信号强度、运营商信息时,再回来改结构就已经比较痛苦了。建议在一开始就预留details对象这样的扩展位置。
- 多端联调时必须真机测试。模拟器上的网络切换场景和真实设备差异很大,这点对OpenHarmony设备尤其明显。
我在实际使用中发现,网络切换提示属于那种“功能简单、细节很多”的模块。第一版能跑通只是开始,真正让它稳定可靠地运行在用户设备上,还需要经过大量真实场景的验证和迭代。对正在做类似功能的开发者,我建议把重心放在“事件链路是否完整、状态判断是否严谨、生命周期是否干净”这三个核心点上。这三点做好了,功能的完成度不会差。
最后再分享一个小技巧:如果你在调试时发现网络状态回调不触发或者触发异常,可以先在原生侧写一个临时的日志文件记录所有回调,再把文件拉出来分析。这种方式比在IDE里断点调试更省力,尤其是处理偶发性网络问题时特别有用。