会议室里的无线网络已经连接,文件提交却一直失败。状态栏有图标,设置页也能看到接入点,开发者很容易顺手写下一句“网络正常,继续查服务器”。把这个常见场景沿着调用链推演一遍,会发现第一句判断就跨了好几层:设备接入网络、系统验证连通性、业务地址可达,根本不是同一个问题。

默认网络与业务可达边界的技术封面

先把那句“网络正常”拆开

排查从一个小工具开始。LinkStatus 不上传文件,也不探测任意网站,它只读取默认网络的能力,告诉开发者目前拿到了什么证据。把业务请求暂时留在工具之外,可以避免一次失败被多个猜测同时解释。页面上的措辞也刻意保留范围:显示系统验证结果,不显示包办一切的“在线”。

默认网络句柄回答的是系统当前选择哪个网络。它并不承诺应用指定绑定的网络与这个句柄相同,也不是设备全部网络接口的清单。一个设备可能同时有无线网络和移动数据,读取默认网络不能推导出另一个网络已经断开。诊断记录必须说明观察对象,否则不同模块的结果看起来会互相矛盾。

接着才是能力标记。互联网能力标记表示网络提供方声明具备相应能力;系统验证标记则来自平台的连通性检查。官方说明中,验证通过表示网络管理模块成功连接了指定的华为云探测地址。缺少这个标记也不等于所有互联网地址都不可达,探测目标与业务目标不能混为一谈。

还有一个很容易漏掉的中间态:系统正在检查连通性。这个阶段验证标记可能尚未反映最终结果,因此应优先展示检查中。需要认证的门户网络也应该单独提示。把所有分支压缩成真假值,界面虽然简单,却会把等待、认证、没有证据和明确取得验证结果混在一起。

本例选择 API 12 及以上 Stage 工程,原因是门户与连通性检查中这两个能力标记从该版本提供。NetworkKit 的 connection 模块负责查询。页面不主动建立业务连接,因此只声明读取网络信息所需的权限;未来加入请求探测时,再按实际能力补充配置。

连接状态还不应决定应用所有功能能否使用。已有文档、本地草稿和离线帮助可能完全不依赖网络;某项业务是否可以发起,也应由它自己的需求决定。若把一个网络布尔值放在首页最外层,断开默认网络时整页变灰,用户就会失去本来仍然可用的能力。诊断信息应该帮助定位,不该无意扩大故障影响。

中午之前,先让分类逻辑说清楚

在 entry/src/main/module.json5 的 module.requestPermissions 数组中合并下面的权限项。它属于系统授权权限,不需要为此弹出运行时授权窗口。权限必须放在实际运行页面所属模块中,不能只修改另一个模块的配置后,凭应用名称相同就认为声明已经生效。

{
  "name": "ohos.permission.GET_NETWORK_INFO"
}

新建 common/NetworkStatus.ets。分类函数只处理已经取得的能力集合,不负责发请求;网络不存在或查询失败由页面处理。将这两层分开后,可以用固定枚举数组验证分支顺序,也能避免把查询异常伪装成“没有互联网能力”。空集合表达的是未报告标记,而不是给网络下最终判决。

// entry/src/main/ets/common/NetworkStatus.ets
import { connection } from '@kit.NetworkKit';

export function describeCaps(caps: connection.NetCap[]): string {
  if (caps.includes(connection.NetCap.NET_CAPABILITY_CHECKING_CONNECTIVITY)) {
    return '系统正在验证连通性';
  }
  if (caps.includes(connection.NetCap.NET_CAPABILITY_PORTAL)) {
    return '网络需要登录认证';
  }
  if (caps.includes(connection.NetCap.NET_CAPABILITY_VALIDATED)) {
    return '系统连通性验证通过';
  }
  if (caps.includes(connection.NetCap.NET_CAPABILITY_INTERNET)) {
    return '已声明互联网能力,尚未取得验证结果';
  }
  return '未报告互联网能力';
}

export function bearerText(types: connection.NetBearType[]): string {
  if (types.includes(connection.NetBearType.BEARER_WIFI)) {
    return '无线网络';
  }
  if (types.includes(connection.NetBearType.BEARER_CELLULAR)) {
    return '移动数据';
  }
  if (types.includes(connection.NetBearType.BEARER_ETHERNET)) {
    return '以太网';
  }
  return '其他或未报告的承载类型';
}

判断顺序是页面的一项明确决策。检查中优先,是因为此时旧的验证标记可能还在;门户提示随后,是因为用户通常需要先完成认证;只有没有前两种情况时,才展示验证通过。函数没有依赖枚举的具体数字,也没有把数组长度当作网络质量分数。能力集合是多项事实,长度本身没有产品意义。

承载类型只用于解释当前网络来自哪里。无线网络不自动等于免费网络,移动数据也不自动等于慢网络。如果产品将来要控制大文件下载,应另行设计计费与用户策略,不能把这里的名称映射变成流量承诺。这个工具越少替其他业务作决定,它的诊断文字反而越容易保持准确。

函数对未报告的类型保留通用描述,避免把未来新增的承载方式错误归入移动数据。对于能力数组,使用枚举成员判断具体事实比匹配完整数组更稳妥,因为系统可能同时返回本文没有使用的标记。应用只解释自己认识的部分,也就无需因为额外能力出现而把整份结果判为错误。

真正读取时,网络可能已经换了

下面是完整页面入口。创建空 Stage 工程后保留模板生成的 Ability 与页面路由,将 pages/Index.ets 替换为此代码,并加入前面的工具文件。初始化不主动查询,第一次点击才读取快照,页面能够区分“尚未检查”和已经检查但未取得网络句柄。

// entry/src/main/ets/pages/Index.ets
import { connection } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { describeCaps, bearerText } from '../common/NetworkStatus';

@Entry
@Component
struct Index {
  @State busy: boolean = false;
  @State network: string = '尚未读取';
  @State status: string = '尚未检查';
  @State detail: string = '点击按钮读取一次默认网络快照';

  async refresh(): Promise<void> {
    if (this.busy) { return; }
    this.busy = true;
    this.network = '正在读取';
    this.status = '正在读取';
    this.detail = '等待网络信息服务返回';
    try {
      const handle = await connection.getDefaultNet();
      if (handle.netId === 0) {
        this.network = '未发现默认网络';
        this.status = '未取得网络句柄';
        this.detail = '可在系统设置中检查连接,然后重新读取';
        return;
      }
      const caps = await connection.getNetCapabilities(handle);
      const current = await connection.getDefaultNet();
      if (current.netId !== handle.netId) {
        this.network = '默认网络发生变化';
        this.status = '本次快照已放弃';
        this.detail = '连接切换后请重新读取';
        return;
      }
      this.network = bearerText(caps.bearerTypes);
      this.status = describeCaps(caps.networkCap ?? []);
      this.detail = '已读取一次能力快照,切换网络后需重新读取';
    } catch (e) {
      const error = e as BusinessError;
      this.network = '读取未完成';
      this.status = '无法取得能力快照';
      this.detail = error.code === 201 ? '请检查网络信息权限声明' : '网络服务或句柄状态变化,请稍后重试';
    } finally {
      this.busy = false;
    }
  }

  build() {
    Scroll() {
      Column({ space: 16 }) {
        Text('网络能力检查').fontSize(28).fontWeight(FontWeight.Bold)
        Text('先描述证据,再判断业务').fontSize(16).fontColor('#64748B')
        Column({ space: 10 }) {
          Text('默认网络').fontSize(15).fontColor('#486879')
          Text(this.network).fontSize(22).fontWeight(FontWeight.Medium)
        }.padding(20).width('100%').backgroundColor('#E7F1F6').borderRadius(16)
        Column({ space: 10 }) {
          Text('系统验证状态').fontSize(15).fontColor('#486879')
          Text(this.status).fontSize(21).fontWeight(FontWeight.Medium)
        }.padding(20).width('100%').backgroundColor('#FFFFFF').borderRadius(16)
        Text(this.detail).fontSize(15).fontColor('#486879').width('100%')
        Button(this.busy ? '读取中' : '读取当前网络').width('100%').height(48)
          .backgroundColor('#386D86').enabled(!this.busy)
          .onClick(() => { this.refresh(); })
        Text('业务接口可达性需要另行判断').fontSize(15).fontColor('#64748B')
      }.padding(20).width('100%')
    }.height('100%').width('100%').backgroundColor('#F3F7FA')
  }
}

读取句柄与读取能力是两次操作,中间可能发生切网。代码在能力查询后再读取一次默认句柄,发现变化就放弃当前结果。这个复查可以排除一类明显失配,却不是跨系统调用的原子快照:同一网络的能力仍可能变化,第二次查询结束后也可能再次切换。页面因此一直使用“本次快照”的表述。

查询失败时,旧的成功文字会被覆盖,避免用户误把上一张快照当作这一次的结果。权限错误给出配置方向,其他失败提醒重读。这里没有失败后无限自动重试,因为快速切网时重复查询可能不断追逐变化,而用户最需要的是知道本次证据不完整。按钮恢复可用后,下一次操作重新开始。

这里也没有请求接入点名称、设备地址或详细路由表。要回答当前页面的两个问题,句柄和能力已经足够。诊断工具应围绕问题选择信息,而不是把能够读取的字段全部搬到界面或日志。后续确实需要更细的网络信息时,再说明它解决哪一个假设,避免原始信息越堆越多,关键判断反而看不清。

网络能力分类函数与初始诊断页面

下午回到那个失败的提交按钮

现在回看会议室场景,排查路线已经改变。如果没有取得默认网络句柄,先处理连接配置;如果系统正在验证,先等状态稳定再读;如果识别到门户,完成网络认证;如果系统验证通过,才说明取得了这一层证据。即使走到最后一步,也不能替业务接口宣布成功。

业务地址还涉及域名解析、路由、代理、证书、身份凭据和服务端状态。系统探测目标可达,不能证明某个组织内部地址可达;业务请求失败,也不能倒推出整台设备完全离线。面向用户的错误文案应尽量指向失败层级,避免所有问题都落到一句“请检查网络”上。

验证这段工具时,先做不依赖设备环境的分类检查。给函数传入检查中与已验证两个标记,应优先返回检查中;传入门户与互联网能力,应提示认证;只传验证通过,应显示验证结果;只传互联网能力,应保留尚未取得验证的含义。这些是人为构造的分支输入,不能写成真实网络观测数据。

再在目标设备上读取实际网络。分别观察没有默认网络、连接稳定和切换连接的过程,记录系统环境与实际返回值,不要求每台设备都出现全部中间状态。门户测试需要确实存在认证流程的网络,不能靠连接一个普通路由器就声称验证了门户分支。切网太快时,查询失败本身也是合理结果。

还要检查页面自身的操作边界。连续快速按读取按钮时,只应存在一个本页查询过程;完成后应恢复可用;一次失败不能永久锁住按钮。由于本例没有注册监听,也没有持有长连接,页面不存在对应的注销调用。若后来加入监听,资源责任就改变了,应作为一次完整功能扩展处理。

故障排查还要避免把无线图标的信号强弱当作链路性能报告。此页没有测量吞吐、时延或丢包,也没有显示任何速度数字。若要评估上传体验,需要明确目标、数据量和测试条件,再加入独立测量过程。网络能力字段能够帮助解释环境,却不能替代业务路径上的真实测量。

默认网络、验证标记与业务失败的排查分层

留下的是一份有范围的证据

这个页面选择手动快照,适合开发辅助与一次性诊断。如果要持续展示连接变化,可以另行使用网络状态监听,并为注册、注销和页面生命周期建立完整约定。不能在快照代码外简单加一个高频定时器,就认为获得了准确的持续网络状态;采样间隙和能力变化仍然存在。

如果应用显式绑定网络或经过特定代理,诊断入口还必须说明它观察的是系统默认网络。否则开发者可能拿无线网络的能力解释另一条实际请求路径。本文保留这个边界,而没有尝试用默认网络查询自动推断所有路由关系。工具名称中的“默认”两个字,应该在界面、记录和团队沟通里一直保留。

回到最初那句“网络正常”,更有用的记录应是:某次查询取得默认网络,系统当时已完成连通性验证,随后某个业务请求在具体阶段失败。这样的描述给下一位排查者留下可继续推理的条件。它不保证问题马上解决,却能阻止团队在不同层级之间反复兜圈。

官方参考

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐