网络状态监听&WiFi信息获取:弱网提示、断网兜底方案

几乎所有鸿蒙应用都要联网,接口请求、图片加载、数据同步都依赖网络。但很多开发者只关注"能不能请求成功",忽略了网络状态变化的处理——用户切到地下车库WiFi断了,页面还在转圈;电梯里信号弱,接口超时了也不提示。最后用户以为APP卡了,直接退出。本文基于最新鸿蒙API,把网络状态监听、WiFi信息获取、弱网提示和断网兜底的完整落地流程讲清楚,代码可以直接复制到项目里跑。

01 权限前置与网络状态监听

网络监听需要先配置权限,获取WiFi信息还需要定位权限,因为WiFi位置信息涉及用户隐私。module.json5里需要声明两个权限:

"requestPermissions": [
  {
    "name": "ohos.permission.GET_NETWORK_INFO",
    "reason": "$string:network_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  },
  {
    "name": "ohos.permission.GET_WIFI_INFO",
    "reason": "$string:wifi_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
]

GET_NETWORK_INFO用来监听网络状态变化,GET_WIFI_INFO用来获取WiFi详细信息。如果还要扫描周边WiFi列表,还需要加定位权限,这里我们只获取当前连接的WiFi信息,不需要扫描。网络状态监听的核心是netConnection对象,注册回调后,网络变化会自动通知:

import { connection } from '@kit.NetworkKit';

const netConn = connection.createNetConnection();

// 网络可用回调
netConn.on('netAvailable', (data: connection.NetCapabilities) => {
  console.info('网络可用,类型:', data.bearerTypes);
  // WiFi: BEARER_WIFI, 蜂窝: BEARER_CELLULAR
});

// 网络丢失回调(断网)
netConn.on('netLost', (data: connection.NetHandle) => {
  console.info('网络已断开');
  // 这里触发断网兜底逻辑
});

// 网络能力变化回调(弱网切换)
netConn.on('netCapabilitiesChange', (data: connection.NetCapabilities) => {
  console.info('网络能力变化:', data.bearerTypes);
  // 可以在这里判断是否从WiFi切到蜂窝
});

// 注册监听,开始接收网络状态变化
netConn.register(true);

// 页面退出时记得反注册,不然会内存泄漏
// netConn.unregister();

几个关键回调的触发时机:

  • netAvailable:网络恢复可用时触发,刚开机、从断网恢复都会走这里

  • netLost:网络完全断开时触发,关WiFi、飞行模式、信号全丢

  • netCapabilitiesChange:网络类型切换时触发,比如WiFi切到4G,或者网络质量变化

register(true)里的true表示立即返回一次当前网络状态,不用等下次变化。

02 获取当前网络类型与WiFi信息

除了监听状态变化,很多场景还需要主动获取当前网络的详细信息:当前是WiFi还是蜂窝、WiFi信号强度是多少、SSID是什么。

import { connection } from '@kit.NetworkKit';
import { wifiManager } from '@kit.ConnectivityKit';

// 获取当前默认网络
async function getCurrentNetwork() {
  const netHandle = connection.getDefaultNet();
  const capabilities = connection.getNetCapabilities(netHandle);

  // 判断网络类型
  const isWifi = capabilities.bearerTypes.includes(
    connection.NetBearType.BEARER_WIFI
  );
  const isCellular = capabilities.bearerTypes.includes(
    connection.NetBearType.BEARER_CELLULAR
  );

  console.info('WiFi:', isWifi, '蜂窝:', isCellular);
  return { isWifi, isCellular, capabilities };
}

WiFi详细信息通过wifiManager获取:

async function getWifiInfo() {
  const wifi = wifiManager.getWifiInstance();
  // 先判断WiFi是否打开
  if (!wifi.isWifiActive()) {
    console.info('WiFi未开启');
    return null;
  }

  const info = wifi.getLinkedInfo();
  console.info('WiFi名称:', info.ssid);
  console.info('信号强度:', info.rssi); // -50很好,-80以下就是弱网
  console.info('连接速度:', info.linkSpeed); // 单位Mbps
  console.info('频率:', info.frequency); // 2.4G还是5G

  return info;
}

rssi是WiFi信号强度,数值越大信号越好:

  • -50以上:信号极好,下载看视频无压力

  • -50到-70:信号良好,正常使用没问题

  • -70到-80:信号偏弱,加载图片会慢

  • -80以下:信号很差,接口容易超时

这个数值可以直接用来判断弱网,不用自己算。

03 弱网检测与提示实现

弱网不是完全断网,而是能用但很慢。用户感觉页面卡、图片加载不出来,但又不知道是网络的问题。这时候主动给个提示,体验会好很多。

弱网判断可以从两个维度入手:WiFi信号强度,以及接口请求耗时。

import { promptAction } from '@kit.ArkUI';

let isWeakNetwork: boolean = false;

// 根据WiFi信号判断弱网
function checkWifiWeakness(rssi: number): boolean {
  // rssi低于-75就算弱网
  return rssi < -75;
}

// 接口请求超时统计,连续超时就算弱网
let timeoutCount: number = 0;
const MAX_TIMEOUT = 2; // 连续2次超时判定弱网

function onRequestSuccess() {
  timeoutCount = 0;
}

function onRequestTimeout() {
  timeoutCount++;
  if (timeoutCount >= MAX_TIMEOUT && !isWeakNetwork) {
    isWeakNetwork = true;
    promptAction.showToast({ message: '当前网络较慢,建议切换网络' });
  }
}

function onNetworkRecover() {
  isWeakNetwork = false;
  timeoutCount = 0;
}

弱网提示不要做成弹窗打断用户,用顶部横幅或者轻量Toast就够了。用户知道网络不好,自然会自己切换。弹窗太强硬反而引起反感。

还有一个细节:弱网时可以自动降低图片加载质量。比如列表页的缩略图,弱网下加载低清版本,WiFi下加载高清版本,这样加载速度会快很多。

04 断网兜底方案设计

完全断网的时候,不能让用户对着一个白屏或者转圈的加载页发呆。断网兜底的核心思路是:检测到断网 → 显示兜底页面 → 网络恢复 → 自动重试。

页面级别的断网兜底,可以封装成一个通用组件:

@Entry
@Component
struct NetworkGuard {
  @State isOffline: boolean = false;
  private netConn: connection.NetConnection = connection.createNetConnection();

  aboutToAppear() {
    // 监听网络状态
    this.netConn.on('netLost', () => {
      this.isOffline = true;
    });
    this.netConn.on('netAvailable', () => {
      if (this.isOffline) {
        this.isOffline = false;
        // 网络恢复,自动重新加载数据
        this.retryLoadData();
      }
    });
    this.netConn.register(true);
  }

  aboutToDisappear() {
    this.netConn.unregister();
  }

  async retryLoadData() {
    // 重新请求当前页面数据
    console.info('网络恢复,自动重试');
  }

  build() {
    Stack() {
      // 正常内容
      Column() {
        Text('页面主内容').fontSize(20)
      }
      .visibility(this.isOffline ? Visibility.Hidden : Visibility.Visible)

      // 断网兜底页
      Column() {
        Image($r('app.media.no_network'))
          .width(120)
          .height(120)
        Text('网络连接已断开')
          .fontSize(18)
          .margin({ top: 16 })
        Button('点击重试')
          .margin({ top: 24 })
          .onClick(() => {
            this.retryLoadData();
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor('#f5f5f5')
      .visibility(this.isOffline ? Visibility.Visible : Visibility.Visible)
    }
    .width('100%')
    .height('100%')
  }
}

兜底页面的几个关键点:

  • 要有重试按钮,用户点一下就能手动刷新,不用退出页面重进

  • 网络恢复自动重试,不用用户手动点,体验更好

  • 不要用弹窗,直接替换整个页面内容,层级更清晰

除了页面兜底,接口请求层面也要做断网处理。请求前先判断当前有没有网络,没网就直接走本地缓存或者返回错误,不用等接口超时,省时间。

05 完整网络管理工具类封装

把上面的逻辑封装成一个统一的网络管理类,项目里直接调用,不用每个页面都写一遍监听逻辑。

import { connection } from '@kit.NetworkKit';
import { wifiManager } from '@kit.ConnectivityKit';

type NetworkChangeListener = (isOnline: boolean, type: string) => void;

class NetworkManager {
  private static instance: NetworkManager;
  private netConn: connection.NetConnection;
  private listeners: NetworkChangeListener[] = [];
  private isOnline: boolean = true;
  private networkType: string = 'unknown';

  private constructor() {
    this.netConn = connection.createNetConnection();
    this.initListener();
  }

  static getInstance(): NetworkManager {
    if (!NetworkManager.instance) {
      NetworkManager.instance = new NetworkManager();
    }
    return NetworkManager.instance;
  }

  private initListener() {
    this.netConn.on('netAvailable', (data) => {
      this.isOnline = true;
      this.networkType = data.bearerTypes.includes(
        connection.NetBearType.BEARER_WIFI
      ) ? 'wifi' : 'cellular';
      this.notifyListeners();
    });

    this.netConn.on('netLost', () => {
      this.isOnline = false;
      this.networkType = 'none';
      this.notifyListeners();
    });

    this.netConn.register(true);
  }

  // 页面注册监听
  addListener(listener: NetworkChangeListener) {
    this.listeners.push(listener);
    // 注册时立刻返回当前状态
    listener(this.isOnline, this.networkType);
  }

  // 页面销毁时移除监听,防止内存泄漏
  removeListener(listener: NetworkChangeListener) {
    this.listeners = this.listeners.filter(l => l !== listener);
  }

  private notifyListeners() {
    this.listeners.forEach(l => l(this.isOnline, this.networkType));
  }

  // 主动判断是否联网
  isNetworkAvailable(): boolean {
    return this.isOnline;
  }

  // 判断是否是WiFi
  isWifi(): boolean {
    return this.networkType === 'wifi';
  }

  // 获取WiFi信号强度
  async getWifiRssi(): Promise<number> {
    const wifi = wifiManager.getWifiInstance();
    if (!wifi.isWifiActive()) return -100;
    return wifi.getLinkedInfo().rssi;
  }
}

// 使用方式
const netMgr = NetworkManager.getInstance();

// 页面里注册监听
netMgr.addListener((isOnline, type) => {
  console.info('网络状态:', isOnline, type);
});

这样封装之后,任何页面只需要addListener就能收到网络变化通知,页面销毁时removeListener就行。不用每个页面都去创建netConnection,也不会出现重复注册的问题。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐