鸿蒙实战:网络状态监听与诊断工具

完整代码:NetworkMonitor 在网络状态查询场景中,我们往往需要实时感知网络变化了解当前网络的带宽和计费状态,并给出智能化建议。本文将实现一个完整的网络工具箱页面,满足这些需求。

一、最终效果

  • 自动获取当前默认网络的:网络类型(Wi‑Fi/蜂窝/以太网)、上行/下行带宽(注:部分网络如Wi‑Fi可能无法提供带宽估算,此时显示"未知")、是否计费。
  • 实时监听:当网络类型切换(Wi‑Fi ↔ 蜂窝)、网络断开或恢复时,界面自动刷新并更新诊断建议。
  • 智能诊断:根据网络带宽和计费状态给出提示(如"当前使用移动数据,请注意流量消耗"或"网速一般")。

二、实现思路

  1. 获取默认网络 :通过 connection.getDefaultNet() 获取当前激活的数据网络句柄。
  2. 查询网络能力 :利用 connection.getNetCapabilities() 获取 NetCapabilities 对象,包含网络类型、带宽、计费标志等。
  3. 事件监听 :使用 NetConnectionnetAvailablenetLostnetCapabilitiesChange 事件实时更新 UI。
  4. 诊断规则:根据下行带宽数值和是否计费给出文字建议。

三、完整代码

1. 权限声明 (module.json5)

json 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.GET_NETWORK_INFO"
  },
  {
    "name": "ohos.permission.INTERNET"
  }
]

2. 网络详情模型 (model/NetworkDetail.ets)

javascript 复制代码
export interface RouteInfo {
  interface: string;
  destination: string;
  gateway: string;
  hasGateway: boolean;
  isDefaultRoute: boolean;
}

export interface NetworkDetail {
  netId: number;
  typeName: string;           // Wi-Fi / 蜂窝网络 / 以太网 / VPN / 其他
  upBandwidth: number;        // 上行带宽 Kbps
  downBandwidth: number;      // 下行带宽 Kbps
  isMetered: boolean;         // 是否计费
  interfaceName: string;      // 网卡名称
  mtu: number;                // 最大传输单元
  ipAddresses: string[];      // 链路 IP 地址列表
  routes: RouteInfo[];        // 路由信息列表
}

3. 网络管理器 (manager/NetworkManager.ets)

javascript 复制代码
import { connection } from '@kit.NetworkKit';
import { NetworkDetail, RouteInfo } from '../model/NetworkDetail';

export class NetworkManager {
  private static listeners: Array<(detail: NetworkDetail | null) => void> = [];
  private static netConnection: connection.NetConnection | null = null;

  static startListening(): void {
    if (NetworkManager.netConnection) NetworkManager.stopListening();
    NetworkManager.netConnection = connection.createNetConnection();

    NetworkManager.netConnection.on('netAvailable', () => NetworkManager.refreshNetwork());
    NetworkManager.netConnection.on('netLost', () => NetworkManager.notifyListeners(null));
    NetworkManager.netConnection.on('netCapabilitiesChange', (info: connection.NetCapabilityInfo) => {
      NetworkManager.fetchDetail(info.netHandle).then((detail: NetworkDetail | null) => {
        NetworkManager.notifyListeners(detail);
      });
    });

    NetworkManager.netConnection.register((err: Error | null) => {
      if (!err) NetworkManager.refreshNetwork();
    });
  }

  static stopListening(): void {
    if (NetworkManager.netConnection) {
      NetworkManager.netConnection.unregister(() => {});
      NetworkManager.netConnection = null;
    }
  }

  static async refreshNetwork(): Promise<void> {
    try {
      const netHandle = await connection.getDefaultNet();
      if (!netHandle || netHandle.netId === 0) {
        NetworkManager.notifyListeners(null);
        return;
      }
      const detail = await NetworkManager.fetchDetail(netHandle);
      NetworkManager.notifyListeners(detail);
    } catch {
      NetworkManager.notifyListeners(null);
    }
  }

  private static async fetchDetail(netHandle: connection.NetHandle): Promise<NetworkDetail | null> {
    return new Promise((resolve) => {
      connection.getNetCapabilities(netHandle, async (err, caps) => {
        if (err) {
          resolve(null);
          return;
        }
        // 解析网络类型
        const bearer = caps.bearerTypes?.[0];
        let typeName = '其他';
        if (bearer === connection.NetBearType.BEARER_WIFI) typeName = 'Wi-Fi';
        else if (bearer === connection.NetBearType.BEARER_CELLULAR) typeName = '蜂窝网络';
        else if (bearer === connection.NetBearType.BEARER_ETHERNET) typeName = '以太网';
        else if (bearer === connection.NetBearType.BEARER_VPN) typeName = 'VPN';

        let isMetered = false;
        if (bearer === connection.NetBearType.BEARER_CELLULAR) {
          isMetered = !(caps.networkCap?.includes(connection.NetCap.NET_CAPABILITY_NOT_METERED) ?? false);
        }

        let props: connection.ConnectionProperties | null = null;
        try {
          props = connection.getConnectionPropertiesSync(netHandle);
        } catch (e) {
          console.error('获取连接属性失败', e);
        }

        const ipAddresses = props?.linkAddresses?.map(addr => addr.address.address) ?? [];

        const routes: RouteInfo[] = (props?.routes ?? []).map((route): RouteInfo => ({
          interface: route.interface,
          destination: route.destination.address.address,
          gateway: route.gateway.address,
          hasGateway: route.hasGateway,
          isDefaultRoute: route.isDefaultRoute,
        }));

        const detail: NetworkDetail = {
          netId: netHandle.netId,
          typeName,
          upBandwidth: caps.linkUpBandwidthKbps ?? -1,
          downBandwidth: caps.linkDownBandwidthKbps ?? -1,
          isMetered,
          interfaceName: props?.interfaceName ?? '',
          mtu: props?.mtu ?? -1,
          ipAddresses,
          routes,
        };
        resolve(detail);
      });
    });
  }

  static addListener(callback: (detail: NetworkDetail | null) => void): void {
    NetworkManager.listeners.push(callback);
  }

  static removeListener(callback: (detail: NetworkDetail | null) => void): void {
    const idx = NetworkManager.listeners.indexOf(callback);
    if (idx !== -1) NetworkManager.listeners.splice(idx, 1);
  }

  private static notifyListeners(detail: NetworkDetail | null): void {
    NetworkManager.listeners.forEach(cb => cb(detail));
  }
}

4. 主页面 (pages/Index.ets)

javascript 复制代码
import { NetworkManager } from '../manager/NetworkManager';
import { NetworkDetail } from '../model/NetworkDetail';

interface InfoItem {
  label: string,
  value: string
}
@Entry
@Component
struct Index {
  @State networkDetail: NetworkDetail | null = null;
  @State adviceText: string = '';

  aboutToAppear(): void {
    NetworkManager.addListener(this.onNetworkChanged);
    NetworkManager.startListening();
  }

  aboutToDisappear(): void {
    NetworkManager.removeListener(this.onNetworkChanged);
    NetworkManager.stopListening();
  }

  onNetworkChanged = (detail: NetworkDetail | null) => {
    this.networkDetail = detail;
    this.adviceText = detail ? this.getAdvice(detail) : '无网络连接,请检查网络设置';
  };

  getAdvice(detail: NetworkDetail): string {
    if (detail.isMetered) return '当前使用移动数据,请注意流量消耗。';
    if (detail.downBandwidth > 0 && detail.downBandwidth < 1000) return '当前网速较慢 (<1 Mbps),可能影响加载速度。';
    if (detail.downBandwidth >= 1000 && detail.downBandwidth < 5000) return '网速一般 (1-5 Mbps),可流畅浏览网页。';
    if (detail.downBandwidth >= 5000) return '网速良好 (≥5 Mbps),适合高清视频和游戏。';
    return '网络状态正常。';
  }

  build() {
    Column({ space: 16 }) {
      Text('网络诊断工具箱')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      if (this.networkDetail) {
        Column({ space: 12 }) {
          this.InfoRow({label:'网络类型', value:this.networkDetail.typeName})
          this.InfoRow({label:'网络ID', value:this.networkDetail.netId.toString()})
          this.InfoRow({label:'上行带宽', value:this.networkDetail.upBandwidth > 0 ? `${this.networkDetail.upBandwidth} Kbps` : '未知'})
          this.InfoRow({label:'下行带宽', value:this.networkDetail.downBandwidth > 0 ? `${this.networkDetail.downBandwidth} Kbps` : '未知'})
          this.InfoRow({label:'计费网络', value:this.networkDetail.isMetered ? '是 (移动数据)' : '否'})
          Divider()
          Text('诊断建议')
            .fontWeight(FontWeight.Bold)
            .alignSelf(ItemAlign.Start)
          Text(this.adviceText)
            .fontSize(14)
            .fontColor('#007AFF')
            .alignSelf(ItemAlign.Start)
        }
        .width('90%')
        .padding(16)
        .backgroundColor('#F8F9FA')
        .borderRadius(16)
        .margin({ top: 20 })
      } else {
        Text('未检测到网络连接,请连接 Wi-Fi 或开启移动数据')
          .margin({ top: 30 })
          .fontColor('#FF3B30')
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  InfoRow(info:InfoItem) {
    Row() {
      Text(info.label).width('35%').fontSize(16)
      Text(info.value).fontSize(16).fontColor('#222')
    }
    .width('100%')
  }
}

四、总结

  1. 实时监听:网络切换、信号变化时自动刷新,无需手动操作。
  2. 带宽诊断:根据下行带宽范围给出人性化提示(<1 Mbps / 1-5 Mbps / ≥5 Mbps)。
  3. 计费提醒:自动识别移动数据网络,提示注意流量消耗。
  4. 生命周期管理:页面销毁时自动停止监听,避免内存泄漏。

如果觉得本文对你有帮助,请点赞、收藏、转发支持!

相关推荐
其实防守也摸鱼2 小时前
软件安全与漏洞--软件安全编码与防御技术理论题库
开发语言·网络·安全·网络安全·软件安全·软件安全与漏洞
Yang96112 小时前
光纤接续零损耗:成都鼎讯 AM-601光纤熔接机在风电能源中的应用
网络·能源
祭曦念2 小时前
从零开始构建鸿蒙纪念日提醒 App:ArkTS + API 24 实战
华为·harmonyos
浮芷.3 小时前
鸿蒙HarmonyOS 6.1新特性之沉浸式光感效果实现过程中的各类问题解决-鸿蒙PC版(一)
华为·harmonyos·鸿蒙·鸿蒙系统
mmmayang3 小时前
基于 QUIC 的 HTTP_3
网络·网络协议·http
AI 编程助手GPT3 小时前
用 Python 做一个世界杯赛前分析脚本:以巴西 vs 摩洛哥为例
开发语言·网络·人工智能·python·chatgpt
轻口味3 小时前
轻规划鸿蒙开发实战7:接管 Ability Kit 跨设备流转,EntryAbility 全链路 onContinue 数据打包与无缝接
华为·harmonyos·鸿蒙
风满城333 小时前
鸿蒙原生应用实战(五):教程、主题与项目总结 — 从开发到上线的完整回顾
harmonyos
ElevenS_it1884 小时前
Nginx日志监控告警实战:access_log解析+5xx突增+慢请求+异常IP自动告警完整方案(Filebeat+Zabbix)
运维·网络·tcp/ip·nginx·zabbix