您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

Android监听网络变化

时间:02-09来源:作者:点击数:

小于Android5.0的版本

小于Android5.0的监听方式,使用广播接收者的方式,代码如下:

context.registerReceiver(ConnectivityReceiver(), IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) // 监听连接改变
class ConnectivityReceiver: BroadcastReceiver() {

    /** 指示是否没有网络 */
    var noConnectivity = false
    var isFirst = true

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onReceive(context: Context, intent: Intent) {
        // 网络连接状态发生变化(Wifi、蜂窝),默认连接已建立或丢失。
        // 受影响的网络的NetworkInfo作为附加发送。应该咨询一下发生了哪种连接事件。

        // 由于NetworkInfo可能因UID而异,因此应用程序应始终通过getActiveNetworkInfo()获取网络信息。
        val networkInfo: NetworkInfo? = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)
        Timber.i("networkInfo = $networkInfo")

        // ConnectivityManager.EXTRA_NETWORK_TYPE 此字段需求api为17,当前最小为15
        val networkType = intent.getIntExtra("networkType", -1)
        Timber.i("networkType = $networkType")

        // 该关键字可提供(可选)有关网络状态的额外信息。信息可以从较低的网络层传递,并且含义可能特定于特定的网络类型。
        val extraInfo = intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO)
        Timber.i("extraInfo = $extraInfo")

        // 用于指示是否完全缺乏连通性,即没有网络可用
        val noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)
        Timber.i("noConnectivity = $noConnectivity")

        // ConnectivityManager.EXTRA_INET_CONDITION 隐藏字段,该键提供有关我们与整个Internet的连接的信息。 0表示没有连接,100表示连接良好
        val inetCondition = intent.getIntExtra("inetCondition", -1)
        Timber.i("inetCondition = $inetCondition")

        Timber.fi("网络发生变化了,noConnectivity = $noConnectivity")

        if (isFirst) {
            // 第一次的网络状态,状态怎样都是要执行的
            this.noConnectivity = noConnectivity
            isFirst = false
        } else if (this.noConnectivity == noConnectivity) {
            // 非第一次的网络状态,需要判断是否与上一次一样,一样的话就不要重复执行了
            // TODO 数据网络切WIFI时不会回调网络断开,而是直接回调网络有效,所以需要增加一个判断:如果之前是数据网络,现在变成了Wifi,则即使网络有连网状态和之前一样也要走后面的代码
            return
        }

        this.noConnectivity = noConnectivity

        if (noConnectivity) {
            // TODO 没网了
        } else {
            // TODO 有网了
        }
    }

}

Android5.0或以上的版本

Android5.0或更高版本的监听方式,使用注册回调的方式,代码如下:

class ConnectivityEngine {

    private val connectivityManager: ConnectivityManager by lazy { ContextHolder.getContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
    private var networkCallback: MyNetworkCallback? = null

    @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
    fun registerNetworkChangeListener(callback: (Boolean, Network) -> Unit) {
        val networkCallback = MyNetworkCallback(connectivityManager, callback).also { this.networkCallback = it }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // 如果Android版本等于7.0(API 24)或以上
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        } else {
            val networkRequest = NetworkRequest.Builder().build()
            connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
        }
    }

    fun unregisterNetworkChangeListener() {
        networkCallback?.let {
            connectivityManager.unregisterNetworkCallback(it)
            networkCallback = null
        }
    }

}
class MyNetworkCallback(private val connectivityManager: ConnectivityManager, private val callback: (Boolean, Network) -> Unit): ConnectivityManager.NetworkCallback() {

    private var isCalledOnLost = true
    private var isCalledOnAvailable = true
    private var uiHandler = Handler(Looper.getMainLooper())

    /** 在框架连接并声明可以使用新网络时调用。(在有网络的情况下注册监听器后这个函数就会立马被调用) */
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        Timber.fi("onAvailable被调用,isCalledOnLost = $isCalledOnLost")
        /*
           网络切换时,拿connectivityManager.activeNetwork来判断是不准确的,
           比如从一个wifi切换到另一个wifi时会先断开当前wifi,然后立马回调onAvailable,网络变成了移动数据网络。
           之后连上另一个wifi时又回调onAvailable,网络变成了wifi网络。
           在变成移动数据网络的时候,拿connectivityManager.activeNetwork来判断是否是移动网络显示为false,
           而使用onAvailable中的参数network来判断则为true。
         */
        connectivityManager.getNetworkCapabilities(network)?.let { capabilities ->
            val isCurrentWifiNetwork = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
            val isCurrentMobileNetwork = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
            val isCurrentVpn = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
            val isInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                val isValidated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                // 插有sim卡且没连wifi时isCellular为true,此时连上wifi,isCellular为false。
                Timber.fi("onAvailable:isInternet = $isInternet, isValidated = $isValidated, isWifi = $isCurrentWifiNetwork, isCellular = $isCurrentMobileNetwork, isVPN = $isCurrentVpn")
            } else {
                Timber.fi("onAvailable:isInternet = $isInternet, isWifi = $isCurrentWifiNetwork, isCellular = $isCurrentMobileNetwork, isVPN = $isCurrentVpn")
            }
        }

        // 在启动VPN时(在Android10上会这样,在Android7.1.1不会有任何的方法回调),启动成功后会直接再回调onAvailable方法(不回调onLost),网络变成WIFI + VPN或进CELLULAR + VPN
        // 注:不要使用VpnManager.isOnline()来判断,这个并不准确。从wifi vpn切换到APN网时,VPN图标还在,而且VpnManager.isOnline()返回true,实际上此时的VPN已经失效
        // 切换网络时,如果VPN没有断开,则不需要做任何处理,因为ip会使用VPN的ip,数据请求权限什么都不会变的。
        // 注:在android10系统中,切换网络后如果VPN没有断开,则不会执行onAvailable方法回调
        // 在Android7.1.1 VPN连着时,切换网络VPN就会断开,在Android10不会断开,但是在Android10不回调onAvailable(注:Android10启动VPN成功后会再回调此方法,这种情况也是不需要执行后面代码的)
        // 从移动数据网络切换到wifi网络,此时系统不会回调onLost方法,而是直接回调onAvailable。此时再断开Wifi会回调onLost方法,接着回调onAvailable变成移动数据网络
        // 在Android10(在Android7.1.1中试验是会调用onAvailable的,因为切换时VPN会断开),开着VPN,移动数据网络切换到wifi,只回调onCapabilitiesChanged方法。如果没开VPN,则会回调onAvailable方法。WIFI开着VPN切换到移动网也只回调onCapabilitiesChanged
        // 开着VPN切换网络只回调onCapabilitiesChanged的前提应该是切换后VPN并没有断开,如果断开了应该会回调onAvailable方法
        // 从一个wifi切换到另一个wifi,先回调onLost,然后onAvailable是移动网络,wifi连上后又回调一次onAvailable是wifi网络

        isCalledOnAvailable = true
        if (!isCalledOnLost) {
            return // 如果没有调用过onLost函数,则不往下执行了,预防onAvailable函数连续执行多次的情况
        }
        isCalledOnLost = false // 预防onAvailable函数连续执行多次的情况
        Timber.fi("有网了")
        uiHandler.post { callback(true, network) }
    }

    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        Timber.fi("onCapabilitiesChanged,networkCapabilities = $networkCapabilities")
    }

    /** 当网络断开连接或不再满足此请求或回调时调用。(在无网络的情况下注册监听器后这个函数不会被调用) */
    override fun onLost(network: Network) {
        super.onLost(network)
        Timber.fi("onLost被调用,isCalledOnAvailable = $isCalledOnAvailable")

        isCalledOnLost = true
        if (!isCalledOnAvailable) {
            return // 如果没有调用过onAvailable函数,则不往下执行了,预防onLost函数连续执行多次的情况
        }
        isCalledOnAvailable = false // 预防onLost函数连续执行多次的情况
        Timber.fi("没网了")
        uiHandler.post { callback(false, network) }
    }

}
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门