前言

网络状态这件事,很多应用都做过,也都吃过亏。状态栏明明显示已经连上网,接口请求还是失败。页面上还挂着 Wi-Fi 图标,后台实际已经切到蜂窝数据。用户看到的是一套状态,业务代码拿到的是另一套状态,问题往往就出在这个时间差上。

鸿蒙 6 里,@ohos.net.connection 已经把同步查询和事件订阅两套能力都给出来了。同步接口可以直接拿当前网络快照,事件订阅负责持续追踪网络变化。getDefaultNetSync() 用来获取默认激活的数据网络,getNetCapabilitiesSync()getConnectionPropertiesSync() 分别拿网络能力信息和网络配置信息,createNetConnection()on()register() 这一组接口则用来监听后续变化。

一、先把显示状态和真实网络状态拆开看

页面里展示网络类型,很多时候拿的是最近一次监听结果或者本地缓存。这个值更新得够快,用户会觉得网络状态很灵。更新得慢一点,页面就会出现短暂错位。网络切换一旦变快,比如 Wi-Fi 断开后迅速切到蜂窝,或者多条链路同时在变化,单靠一层异步监听很难保证关键业务点看到的一定是最新值。

这也是同步查询接口最有价值的地方。getDefaultNetSync() 可以直接拿到当前默认激活的数据网络句柄,getNetCapabilitiesSync(netHandle) 可以继续拿这个网络的能力信息,判断网络类型、是否通过互联网可达性验证、上下行带宽等状态。网络信息查询最佳实践里也明确给出了这套用法,并且专门提到,networkCap 里如果包含 connection.NetCap.NET_CAPABILITY_VALIDATED,就说明该网络具备访问互联网的能力。

这里有一个很实际的改法。原文代码把 NET_CAPABILITY_VALIDATED 当成字符串放进 includes() 里判断,这种写法不稳。当前接口和最佳实践里用的是枚举值 connection.NetCap.NET_CAPABILITY_VALIDATED。代码最好按这个口径收。

import { connection } from '@kit.NetworkKit'

// 在关键业务动作前同步获取网络快照
function canAccessInternet(): boolean {
  const netHandle = connection.getDefaultNetSync()
  if (!netHandle || netHandle.netId < 100) {
    return false
  }

  const netCapabilities = connection.getNetCapabilitiesSync(netHandle)
  return netCapabilities.networkCap.includes(
    connection.NetCap.NET_CAPABILITY_VALIDATED
  )
}

function runCriticalRequest() {
  if (canAccessInternet()) {
    executeDataOperation()
  } else {
    fallbackToLocalCache()
  }
}

这种写法更适合放在上传、支付、提交表单、发消息这类关键决策点上。页面图标可以稍微滞后一点,关键业务入口尽量走一次同步快照,结果会稳很多。

二、同步查询解决的是关键时刻,持续感知还得靠事件订阅

同步查询拿到的是某一个时间点的快照,适合做决策前校验。应用如果还要持续跟着网络状态变化更新页面、刷新缓存、切换音视频策略,还是要把事件订阅接上。createNetConnection() 用来创建网络连接监听对象,on() 负责订阅事件,register() 负责正式注册监听。最佳实践里也明确写了,register() 需要在 on() 之后调用。

原文里用了 netAvailablenetLost,这两个事件名是对的。文档和相关指导页都给出了这组事件,另外还有 netUnavailable 这类场景化事件。实际工程里,最常用的仍然是 netAvailablenetLost 和能力变化后的再查询。

更稳的做法,是在事件到来时再补一次同步查询,把事件和快照收成同一份状态。这样做的好处很直接,页面层、业务层、缓存层都只认一份最新网络状态,不会一边用事件值,一边用旧缓存。

import { connection } from '@kit.NetworkKit'

type AppNetworkState = {
  netId: number
  type: connection.NetBearType[]
  validated: boolean
  updatedAt: number
}

let currentNetworkState: AppNetworkState | null = null

function refreshNetworkState(netHandle: connection.NetHandle) {
  const capabilities = connection.getNetCapabilitiesSync(netHandle)
  currentNetworkState = {
    netId: netHandle.netId,
    type: capabilities.bearerTypes,
    validated: capabilities.networkCap.includes(
      connection.NetCap.NET_CAPABILITY_VALIDATED
    ),
    updatedAt: Date.now()
  }
}

const netConnection = connection.createNetConnection()

netConnection.on('netAvailable', (netHandle) => {
  refreshNetworkState(netHandle)
})

netConnection.on('netLost', () => {
  currentNetworkState = null
})

netConnection.register()

这段代码里有两个点很重要。第一,事件回调里不要只存一个简单布尔值,最好顺手把能力信息一起刷新。第二,networkType 这种字段并不是当前主要判断入口,实际项目更常用的是 bearerTypesnetworkCap。最佳实践里用的也是这组字段。(developer.huawei.com)

三、网络类型判断不要停在 Wi-Fi 和蜂窝两档

很多项目的网络判断逻辑很粗,只有 Wi-Fi 和非 Wi-Fi 两档。这种写法前几年还能用,现在已经明显不够。NetCapabilities 里除了网络类型相关信息,还有上下行带宽、能力标记、可达性验证结果等。当前文档里提到,getNetCapabilitiesSync() 能拿到网络类型、网络是否可用等信息,getConnectionPropertiesSync() 可以拿到 IP、网关、DNS 等配置数据。(developer.huawei.com; developer.huawei.com)

这意味着很多业务策略可以做得更细。比如视频预加载逻辑,可以在网络已验证且带宽较高时启用高清流。消息同步可以在网络已连接但还没验证完成时先走本地排队。下载器可以根据当前 bearer 类型决定是否继续大文件传输。这里真正值得判断的,不只是连没连上网,而是当前这条链路适不适合执行某个动作。(developer.huawei.com)

如果项目还需要拿 IP、网关、DNS 或代理等信息,再继续往下接 getConnectionPropertiesSync(netHandle) 就够了。这类信息更适合放在诊断页、日志采集、网络故障排查模块里,不必每次页面刷新都去取。(developer.huawei.com; developer.huawei.com)

import { connection } from '@kit.NetworkKit'

function buildNetworkSnapshot() {
  const netHandle = connection.getDefaultNetSync()
  if (!netHandle || netHandle.netId < 100) {
    return null
  }

  const capabilities = connection.getNetCapabilitiesSync(netHandle)
  const properties = connection.getConnectionPropertiesSync(netHandle)

  return {
    netId: netHandle.netId,
    bearerTypes: capabilities.bearerTypes,
    validated: capabilities.networkCap.includes(
      connection.NetCap.NET_CAPABILITY_VALIDATED
    ),
    upstreamKbps: capabilities.linkUpBandwidthKbps,
    downstreamKbps: capabilities.linkDownBandwidthKbps,
    dnses: properties.dnses,
    routes: properties.routes
  }
}

这类快照更适合拿来做统一状态源。页面展示、上传下载、播放器策略、日志系统都从这份快照拿值,状态同步会简单很多。

四、工程里真正要关注的是状态来源

网络状态错位的问题,很多时候不是接口能力不够,而是应用里有太多来源同时在写网络状态。一个页面用监听值,一个模块用本地缓存,一个上传器又自己重新查一遍,几层逻辑各自维护,结果很快就会不一致。

更稳的做法,是把网络状态收成一层统一的状态源。事件订阅负责持续刷新,同步查询负责关键动作前校验,业务层只拿统一状态,不自己再发明一套判断逻辑。这样做之后,页面展示和真实链路的差距会明显缩小,问题排查也会轻很多。华为的网络信息查询最佳实践,本身也在引导开发者把同步查询和监听注册配合起来用。

如果项目对实时性要求更高,可以再加一层简单约束。比如状态快照超过一定时间就视为过期,关键请求前强制重新同步查询;或者页面显示值来自缓存,真正提交动作前再做一次 getDefaultNetSync()getNetCapabilitiesSync() 双重校验。这些手段实现起来不复杂,收益通常很直接。

总结

鸿蒙 6 API 20 这套网络连接管理能力,关键点就在两组接口上。一组是同步查询,拿当前网络快照;一组是事件订阅,持续跟踪变化。getDefaultNetSync()getNetCapabilitiesSync()getConnectionPropertiesSync() 解决的是当前时刻看到的网络状态,createNetConnection()on()register() 解决的是后续变化怎么接住。

工程里更值得先做的事情也很明确。把页面展示和业务决策拆开,关键动作前走同步查询,页面和模块统一消费同一份网络状态快照。这样收完之后,网络类型显示和实际链路之间的错位会少很多,应用在复杂网络环境里的行为也会稳很多。

Logo

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

更多推荐