Android网络状态获取及NetworkCallback简述

前言

在APP的开发中,获取到网络的链接状态是一个经常使用到的方法。除了可以使用ping指令来判断当前的网络状况之外,还可以直接通过ConnectivityManager来对网络状态进行判断。

一、网络判断旧方法(deprecated)

权限申请

如果要获取网络信息,首先是需要申请网络权限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
判断网络

使用ConnectivityManager可以十分方便的直接判断网络,调用方法如下:

    //判断网络是否连接
    fun isNetworkConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val mNetworkInfo = mConnectivityManager.activeNetworkInfo
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable
            }
        }
        return false
    }
        //判断WiFi是否连接
    fun isWifiConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val mWiFiNetworkInfo = mConnectivityManager
                .getNetworkInfo(ConnectivityManager.TYPE_WIFI)
            if (mWiFiNetworkInfo != null) {
                return mWiFiNetworkInfo.isAvailable
            }
        }
        return false
    }

    //判断移动网络是否连接
    fun isMobileConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val mMobileNetworkInfo = mConnectivityManager
                .getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
            if (mMobileNetworkInfo != null) {
                return mMobileNetworkInfo.isAvailable
            }
        }
        return false
    }

    //获取连接网络的网络信息
    fun getConnectedType(context: Context?): Int {
        if (context != null) {
            val mConnectivityManager = context
                .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val mNetworkInfo = mConnectivityManager.activeNetworkInfo
            if (mNetworkInfo != null && mNetworkInfo.isAvailable) {
                return mNetworkInfo.type
            }
        }
        return -1
    }

但是在完成代码之后却发现有些方法已经被标记为deprecated了,如isAvailable()getActiveNetworkInfo()方法。查看代码注释,其中提示我们应该使用ConnectivityManager.NetworkCallback去监听网络连接变化,并使用registerNetworkCallback去注册监听,内容如下:

     * @deprecated Apps should instead use the
     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
     *             learn about connectivity changes.
     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
     *             {@link ConnectivityManager#registerNetworkCallback}. These will
     *             give a more accurate picture of the connectivity state of
     *             the device and let apps react more easily and quickly to changes.

接下来,我们对此网络监听api进行简单说明。

二、网络监听

ConnectivityManager.NetworkCallback是一个抽象类,具有如下几个方法:

方法 描述
onAvailable(network: Network) 网络连接成功回调
onUnavailable() 网络连接超时或网络不可达
onLost(Network network) 网络已断开连接
onLosing(Network network, int maxMsToLive) 网络正在丢失连接
onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) 网络状态变化
onLinkPropertiesChanged(Network network, LinkProperties linkProperties) 网络连接属性变化
onBlockedStatusChanged(Network network, boolean blocked) 访问的网络阻塞状态发生变化

其中最为常用的方法为onAvailable,onLost,onCapabilitiesChanged方法。

我们定义一个继承自ConnectivityManager.NetworkCallback的子类:NetUtil,示例代码如下:

package com.example.demowork1.util

import android.content.Context
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities


class NetUtil: ConnectivityManager.NetworkCallback() {

    //网络连接成功
    override fun onAvailable(network: Network) {
        LogUtil.instance.d("网络连接成功")
        super.onAvailable(network)
    }

    //网络已断开连接
    override fun onLost(network: Network) {
        LogUtil.instance.d("网络已断开连接")
        super.onLost(network)
    }

    override fun onLosing(network: Network, maxMsToLive: Int) {
        LogUtil.instance.d("网络正在断开连接")
        super.onLosing(network, maxMsToLive)
    }

    //无网络
    override fun onUnavailable() {
        LogUtil.instance.d("网络连接超时或者网络连接不可达")
        super.onUnavailable()
    }

    //当网络状态修改(网络依然可用)时调用
    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        LogUtil.instance.d( "net status change! 网络连接改变")
    }

    //当访问的网络被阻塞或者解除阻塞时调用
    override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
        super.onBlockedStatusChanged(network, blocked)
    }

    //当网络连接属性发生变化时调用
    override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
        super.onLinkPropertiesChanged(network, linkProperties)
    }
    ...
}

onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities)是我们较为常用的网络状态发生变化时的监听,其中的参数networkCapabilities包含有一些我们常用的api ,其中保罗有两个重要方法,如下:

  1. hasCapability
  2. hasTransport

hasCapability可以判断网络是否连接,有多个参数(参数可以查看接口NetCapability),其中常用参数如下:

参数 说明
NetworkCapabilities.NET_CAPABILITY_INTERNET 表示是否连接上了互联网(不关心是否可以上网)
NetworkCapabilities.NET_CAPABILITY_VALIDATED 表示能够和互联网通信(这个为true表示能够上网)

hasTransport可以判断网络的类型,同样有多个参数(参数可以查看接口Transport),其中常用的参数如下:

参数 说明
TRANSPORT_WIFI 表示接入的是WIFI网络
TRANSPORT_CELLULAR 表示接入的是数据网络
TRANSPORT_BLUETOOTH 表示接入的是蓝牙

具体的调用示例如下:

    //当网络状态修改(网络依然可用)时调用
    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        LogUtil.instance.d( "net status change! 网络连接改变")
        // 表明此网络连接成功验证
        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                // 使用WI-FI
                LogUtil.instance.d("当前在使用WiFi上网")
            } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                // 使用数据网络
                LogUtil.instance.d("当前在使用数据网络上网")
            } else{
                LogUtil.instance.d("当前在使用其他网络")
                // 未知网络,包括蓝牙、VPN等
            }
        }
    }
监听调用

具体的调用实现如下:

    private fun setNetListener() {
        var request = NetworkRequest.Builder().build()
        var connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        connMgr.registerNetworkCallback(request, NetUtil.instance)
    }

三、 同步网络状态获取

需要注意的是,虽然Google提供了此Callback方法来获取网络状态的,但是此回调是异步的,如果需要同步获取网络状态,需要调用如下方法getNetworkCapabilities(Network network)方法来获取当前NetWork的连接状态,示例代码如下:

                val network = mConnectivityManager.activeNetwork ?: return false
                val status = mConnectivityManager.getNetworkCapabilities(network)
                        ?: return false
                if (status.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                    return true
                }

但是此方法是出现在Android M之后的SDK中,所以同步获取网络连接状态的代码还需要对Android的版本进行判断,最终代码如下:

    /**
     * 判断网络是否连接
     */
    fun isNetworkConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                    .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                val mNetworkInfo = mConnectivityManager.activeNetworkInfo
                if (mNetworkInfo != null) {
                    return mNetworkInfo.isAvailable
                }
            } else {
                val network = mConnectivityManager.activeNetwork ?: return false
                val status = mConnectivityManager.getNetworkCapabilities(network)
                        ?: return false
                if (status.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                    return true
                }
            }
        }
        return false
    }

    /**
     * 判断是否是WiFi连接
     */
    fun isWifiConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                    .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                val mWiFiNetworkInfo = mConnectivityManager
                        .getNetworkInfo(ConnectivityManager.TYPE_WIFI)
                if (mWiFiNetworkInfo != null) {
                    return mWiFiNetworkInfo.isAvailable
                }
            } else {
                val network = mConnectivityManager.activeNetwork ?: return false
                val status = mConnectivityManager.getNetworkCapabilities(network)
                        ?: return false
                if (status.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                    return true
                }
            }
        }
        return false
    }

    /**
     * 判断是否是数据网络连接
     */
    fun isMobileConnected(context: Context?): Boolean {
        if (context != null) {
            val mConnectivityManager = context
                    .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                val mMobileNetworkInfo = mConnectivityManager
                        .getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
                if (mMobileNetworkInfo != null) {
                    return mMobileNetworkInfo.isAvailable
                }
            } else {
                val network = mConnectivityManager.activeNetwork ?: return false
                val status = mConnectivityManager.getNetworkCapabilities(network)
                        ?: return false
                status.transportInfo
                if (status.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                    return true
                }
            }
        }
        return false
    }

总结

本文主要是对于获取网络状态以及网络监听进行说明,需要注意的是对于同步和异步的区分。

参考文章

google官方文档

推荐阅读更多精彩内容