读取网络状态

Android 允许应用了解连接的动态变化。请使用以下类来跟踪和响应连接变化:

  • ConnectivityManager 告知您的应用系统中的连接状态。
  • Network 类表示设备连接到的一个网络。您可以将 Network 对象用作键,以通过 ConnectivityManager 收集网络的相关信息或绑定网络上的套接字。如果网络连接中断,Network 对象将不再可用。即使设备之后重新连接到同一设备,新的 Network 对象也将表示新网络。
  • LinkProperties 对象包含有关网络链接的信息,例如 DNS 服务器列表、本地 IP 地址以及针对网络安装的网络路由。
  • NetworkCapabilities 对象包含有关网络属性的信息,例如传输方式(Wi-Fi、移动网络、蓝牙)以及网络能力。例如,您可以查询该对象,以确认网络是否能够发送彩信、是否通过强制门户接入,或者是否按流量计费。

希望在任何给定时间都了解当前连接状态的应用可以调用 ConnectivityManager 方法来找出哪种网络可用。这些方法有助于进行调试,也可用于偶尔查看给定时间内可用连接的快照。

然而,同步 ConnectivityManager 方法不会告知您的应用有关调用之后发生的任何情况,因此您无法借助这些方法更新界面。它们也无法在网络断开连接或网络功能发生变更时调整应用行为。

连接随时可能发生变化,并且大多数应用都需要始终知晓设备的最新网络连接状态。应用可以向 ConnectivityManager 注册一个回调,以便在发生应用关注的变化时收到提醒。使用该回调,您的应用可以立即对连接的任何相关变化做出反应,而无需执行可能错过快速更新且成本高昂的轮询。

使用 NetworkCallback 以及其他了解设备连接状态的方式不需要任何特定权限。但是,使用某些网络可能需要特定权限。例如,可能存在应用无法使用的受限网络。绑定到后台网络需要 CHANGE_NETWORK_STATE 权限。某些调用可能需要特定权限才能运行。如需了解详情,请参阅各个调用对应的具体文档。

获取瞬时状态

一台 Android 设备可以同时保持多个连接。如需获取有关当前网络状态的信息,请先获取 ConnectivityManager 的实例:

Kotlin

val connectivityManager = getSystemService(ConnectivityManager::class.java)

Java

ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);

接下来,使用此实例获取对应用当前默认网络的引用:

Kotlin

val currentNetwork = connectivityManager.getActiveNetwork()

Java

Network currentNetwork = connectivityManager.getActiveNetwork();

通过对网络的引用,您的应用可以请求有关网络的信息:

Kotlin

val caps = connectivityManager.getNetworkCapabilities(currentNetwork)
val linkProperties = connectivityManager.getLinkProperties(currentNetwork)

Java

NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(currentNetwork);
LinkProperties linkProperties = connectivityManager.getLinkProperties(currentNetwork);

如需更多实用功能,请注册 NetworkCallback。如需详细了解如何注册网络回调,请参阅监听网络事件

NetworkCapabilities 和 LinkProperties

NetworkCapabilitiesLinkProperties 对象可以提供系统了解的关于某个网络的所有属性。

LinkProperties 对象可以提供关于路由、链接地址、接口名称、代理信息(如有)和 DNS 服务器的信息。针对 LinkProperties 对象调用相关方法可以检索所需信息。

NetworkCapabilities 对象封装了有关网络传输及其功能的信息。

传输是网络运行的物理媒介的抽象形式。常见的传输示例包括以太网、Wi-Fi 和移动网络。VPN 和点对点 Wi-Fi 也可以传输。在 Android 上,一个网络可以同时拥有多个传输。例如,通过 Wi-Fi 和移动网络运行的 VPN。VPN 支持 Wi-Fi、移动网络和 VPN 传输。若要查找某个网络是否具有特定的传输,请使用 NetworkCapabilities.hasTransport(int) 方法和其中一个 NetworkCapabilities.TRANSPORT_* 常量。

功能描述了网络的属性。示例功能包括 MMSNOT_METEREDINTERNET。具有 MMS 功能的网络可以收发彩信消息,不具有此功能的网络则不能。具有 NOT_METERED 功能的网络不会向用户收取流量费用。您的应用可以使用 NetworkCapabilities.hasCapability(int) 和其中一个 NetworkCapabilities.NET_CAPABILITY_* 常量来检查功能是否恰当。

最实用的 NET_CAPABILITY_* 常量包括:

  • NET_CAPABILITY_INTERNET:表示网络设置为访问互联网。这只是设置,而不是实际能够到达公共服务器。例如,网络可以设置为访问互联网,但受到强制门户的限制。

    运营商的移动网络通常具有 INTERNET 功能,而本地点对点 Wi-Fi 网络通常没有。如需了解实际连接,请参阅 NET_CAPABILITY_VALIDATED

  • NET_CAPABILITY_NOT_METERED:表示网络不按流量计费。当用户由于资金、流量限制或电池性能问题而对严重流量消耗敏感时,可将网络归类为按流量计费的网络。

  • NET_CAPABILITY_NOT_VPN:表示网络不是虚拟专用网。

  • NET_CAPABILITY_VALIDATED:表示在系统探测网络时网络提供对公共互联网的实际访问。通过强制门户接入的网络或不提供域名解析的网络则不具备此功能。这是系统关于实际提供访问的网络所能知道的最接近真实情况的信息,尽管通过验证的网络原则上仍需要经过基于 IP 的过滤,或者由于信号差等问题而导致连接突然中断。

  • NET_CAPABILITY_CAPTIVE_PORTAL:表示在系统探测网络时网络具有强制门户。

还存在更多专用应用可能感兴趣的其他功能。如需了解详情,请参阅 NetworkCapabilities.hasCapability(int) 中的参数定义。

网络的功能可能随时发生变化。当系统检测到强制门户时,它会显示一条邀请用户登录的通知。在此过程中,网络具有 NET_CAPABILITY_INTERNETNET_CAPABILITY_CAPTIVE_PORTAL 功能,但不具有 NET_CAPABILITY_VALIDATED 功能。

用户执行操作并登录到强制门户页面后,设备将能够访问公共互联网,并且网络将获得 NET_CAPABILITY_VALIDATED 功能而失去 NET_CAPABILITY_CAPTIVE_PORTAL 功能。

同样,网络的传输可能动态变化。例如,VPN 可以自行重新配置,以使用刚刚出现的速度更快的网络,比如针对底层网络将移动网络切换为 Wi-Fi。在这种情况下,网络会失去 TRANSPORT_CELLULAR 传输而获得 TRANSPORT_WIFI 传输,同时保留 TRANSPORT_VPN 传输。

监听网络事件

如需了解网络事件,请将 NetworkCallback 类与 ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback)ConnectivityManager.registerNetworkCallback(NetworkCallback) 结合使用。这两种方法的用途不同。

所有 Android 应用都有一个由系统确定的默认网络。系统通常首选不按流量计费的网络而非按流量计费的网络,首选网速较快的网络而非网速较慢的网络。

当应用发出网络请求(例如使用 HttpsURLConnection)时,系统会使用默认网络满足该请求。应用也可以通过其他网络发送流量。如需了解详情,请参阅其他网络部分。

在应用的整个生命周期内,设置为默认网络的网络可能随时发生变化。典型的例子是设备处于一个已知活跃、不按流量计费、速度快于移动网络的 Wi-Fi 接入点的覆盖范围内。设备会连接到此接入点,并将所有应用的默认网络切换到新的 Wi-Fi 网络。

当新网络成为默认网络时,应用打开的任何新连接都会使用此网络。一段时间后,上一个默认网络上的所有剩余连接都将被强制终止。如果知道默认网络发生变化的时间对应用很重要,它会按如下方式注册默认网络回调:

Kotlin

connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network : Network) {
        Log.e(TAG, "The default network is now: " + network)
    }

    override fun onLost(network : Network) {
        Log.e(TAG, "The application no longer has a default network. The last default network was " + network)
    }

    override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
        Log.e(TAG, "The default network changed capabilities: " + networkCapabilities)
    }

    override fun onLinkPropertiesChanged(network : Network, linkProperties : LinkProperties) {
        Log.e(TAG, "The default network changed link properties: " + linkProperties)
    }
})

Java

connectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        Log.e(TAG, "The default network is now: " + network);
    }

    @Override
    public void onLost(Network network) {
        Log.e(TAG, "The application no longer has a default network. The last default network was " + network);
    }

    @Override
    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        Log.e(TAG, "The default network changed capabilities: " + networkCapabilities);
    }

    @Override
    public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
        Log.e(TAG, "The default network changed link properties: " + linkProperties);
    }
});

新网络成为默认网络后,应用会收到对新网络的 onAvailable(Network) 的调用。实现 onCapabilitiesChanged(Network,NetworkCapabilities)onLinkPropertiesChanged(Network,LinkProperties) 或者同时实现两者,以针对连接的变化做出相应反应。

对于通过 registerDefaultNetworkCallback() 注册的回调,onLost() 表示网络失去成为默认网络的资格。它可能会断开连接。

虽然您可以通过查询 NetworkCapabilities.hasTransport(int) 来了解默认网络正在使用的传输,但这不足以代表网络的带宽或按流量计费性。您的应用不能假设 Wi-Fi 始终不按流量计费,而且始终提供比移动网络更好的带宽。

而应使用 NetworkCapabilities.getLinkDownstreamBandwidthKbps() 测量带宽,并且使用 NetworkCapabilites.hasCapability(int)NET_CAPABILITY_NOT_METERED 参数来确定按流量计费性。如需了解详情,请参阅 NetworkCapabilities 和 LinkProperties 部分。

默认情况下,回调方法会在应用的连接线程上被调用,这是 ConnectivityManager 使用的一个单独线程。如果回调的实现需要执行时间更长的工作,请使用变体 ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback, Handler) 在单独的工作器线程上调用它们。

当您不再需要使用回调时,请通过调用 ConnectivityManager.unregisterNetworkCallback(NetworkCallback) 来取消注册。您的主 activity 的 onPause() 非常适合执行这项操作,尤其是在 onResume() 中注册回调时。

其他网络

虽然默认网络是大多数应用的唯一相关网络,但某些应用可能希望使用其他可用网络。为了查找这些网络,应用会构建与其需求匹配的 NetworkRequest,然后调用 ConnectivityManager.registerNetworkCallback(NetworkRequest, NetworkCallback)

该流程与监听默认网络类似。不过,尽管在任何给定时间只能有一个默认网络应用于某个应用,但此方法可让您的应用同时看到所有可用网络,因此,调用 onLost(Network) 表示网络已永久断开连接,而非表示它不再是默认网络。

应用会构建 NetworkRequest 以告知 ConnectivityManager 它想要监听的网络类型。以下示例展示了如何为仅关注不按流量计费的互联网连接的应用构建 NetworkRequest

Kotlin

val request = NetworkRequest.Builder()
  .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
  .addCapability(NET_CAPABILITY_INTERNET)
  .build()

connectivityManager.registerNetworkCallback(request, myNetworkCallback)

Java

NetworkRequest request = new NetworkRequest.Builder()
  .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
  .addCapability(NET_CAPABILITY_INTERNET)
  .build();

connectivityManager.registerNetworkCallback(request, myNetworkCallback);

这意味着您的应用知道关于系统中任何不按流量计费的网络的所有变化。

对于默认网络回调,可以调用接受 HandlerregisterNetworkCallback(NetworkRequest, NetworkCallback, Handler),这样就不会加载应用的 Connectivity 线程。

当回调不再相关时,调用 ConnectivityManager.unregisterNetworkCallback(NetworkCallback)。一个应用可以同时注册多个网络回调。

为方便起见,NetworkRequest 对象包含大多数应用所需的常见功能,其中包括:

编写应用时,请检查默认值,以确定它们与您的用例是否匹配;如果您希望在应用遇到没有这些功能的网络时收到相关通知,请清除默认值。相反,您的应用应添加功能,以免在您的应用不会与之交互的网络出现任何连接变化时收到通知。

例如,如果您的应用需要发送彩信,则应将 NET_CAPABILITY_MMS 添加到其 NetworkRequest 中,以免在遇到所有无法发送彩信的网络时都会收到通知。如果您的应用仅对点对点 Wi-Fi 连接感兴趣,请添加 TRANSPORT_WIFI_AWARE。如果您希望使用互联网上的服务器传输数据,则 NET_CAPABILITY_INTERNETNET_CAPABILITY_VALIDATED 非常有用。

回调序列示例

本部分介绍应用在具有移动网络连接的设备上同时注册默认回调和常规回调时可能会获得的回调序列。在此示例中,设备会连接到稳定的 Wi-Fi 接入点,然后断开连接。该示例还假定设备已启用始终开启移动数据设置。

时间表如下:

  1. 当应用调用 registerNetworkCallback() 时,回调会立即收到来自移动网络的 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用,因为只有该网络可用。如果有其他网络可用,应用还会收到另一个网络的回调。

    显示注册网络回调事件和由该事件触发的回调的状态图
    图 1. 调用 registerNetworkCallback() 后的应用状态。

  2. 然后,应用调用 registerDefaultNetworkCallback()。默认网络回调开始接收对移动网络的 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用,因为移动网络是默认网络。如果还有另一个非默认网络在运行,应用无法接收对该非默认网络的调用。

    显示注册默认网络回调事件和由该事件触发的回调的状态图
    图 2. 注册默认网络后的应用状态。

  3. 之后,设备会连接到(不按流量计费的)Wi-Fi 网络。常规网络回调会收到对 Wi-Fi 网络的 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用。

    显示在应用连接到新网络时触发的回调的状态图
    图 3. 连接到不按流量计费的 Wi-Fi 网络后的应用状态。

  4. 此时,Wi-Fi 网络可能需要花一些时间进行验证。在本例中,常规网络回调的 onNetworkCapabilitiesChanged() 调用不包括 NET_CAPABILITY_VALIDATED 功能。很快,它会收到对 onNetworkCapabilitiesChanged() 的调用,其中的新功能将包含 NET_CAPABILITY_VALIDATED。在大多数情况下,验证都非常快。

    当 Wi-Fi 网络通过验证后,系统会优先选择 Wi-Fi 网络而非移动网络,主要因为前者不按流量计费。Wi-Fi 网络成为默认网络,因此默认网络回调会收到对 Wi-Fi 网络的 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用。移动网络进入后台,常规网络回调会收到对移动网络的 onLosing() 的调用。

    由于本示例假设该设备始终开启移动数据,因此移动网络不会断开。如果关闭此设置,一段时间后移动网络就会断开连接,常规网络回调将收到对 onLost() 的调用。

    显示 Wi-Fi 网络连接验证时触发的回调的状态图
    图 4. Wi-Fi 网络验证后的应用状态。

  5. 之后,设备仍会突然断开与 Wi-Fi 的连接,因为它超出了该网络的覆盖范围。由于 Wi-Fi 已断开连接,因此常规网络回调会收到对 Wi-Fi 的 onLost() 的调用。由于移动网络是新的默认网络,因此默认网络回调会收到对移动网络的 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用。

    显示在 Wi-Fi 网络连接中断时触发的回调的状态图
    图 5. 断开 Wi-Fi 网络后的应用状态。

如果已经关闭始终开启移动数据设置,那么在 Wi-Fi 网络断开连接后,设备会尝试重新连接到移动网络。情况是类似的,但 onAvailable() 调用会有短暂的额外延迟,常规网络回调也会收到对 onAvailable()onNetworkCapabilitiesChanged()onLinkPropertiesChanged() 的调用,因为移动网络变为可用。

使用网络进行数据传输的限制

能够通过网络回调看到某个网络并不意味着您的应用可以使用该网络进行数据传输。某些网络不提供互联网连接,而有些网络可能仅限特权应用使用。如需检查互联网连接,请参阅 NET_CAPABILITY_INTERNETNET_CAPABILITY_VALIDATED

后台网络的使用也要经过权限检查。如果您的应用需要使用后台网络,则需要 CHANGE_NETWORK_STATE 权限。

具有此权限的应用还可以让系统尝试启动当前未启动的网络,例如在设备连接到 Wi-Fi 网络的情况下尝试启动移动网络。此类应用会调用 ConnectivityManager.requestNetwork(NetworkRequest, NetworkCallback),在网络启动后会调用 NetworkCallback