读取网络状态

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

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

希望在任何给定时间都了解当前连接状态的应用可以调用 ConnectivityManager 方法来找出哪种网络可用。这些方法有助于进行调试,也可用于偶尔查看给定时间内可用连接的快照。然而,同步 ConnectivityManager 方法不会告知您的应用有关调用之后发生的任何情况,因此您无法借助这些方法更新界面,也无法在网络断开连接或网络功能发生变更时调整应用行为。

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

获取瞬时状态

一台 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;此类网络有 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 它想要监听的网络类型。例如,如果您的应用只希望使用不按流量计费的互联网连接:

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 中,以免被告知所有网络均无法发送彩信,或者在应用只想采用 P2P 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