如何正确监听网络连接断开、网络类型

最近做了一个需求,监听网络连接断开,并根据连接的网络类型实时显示对应状态给到用户

监听网络连接或断开

查了下AI,给到的建议基本是如下类似实现

    fun registerNetworkListener(){
        (Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).apply {
            val request = NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .build()
            registerNetworkCallback(request, object: ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    logger.i("onAvailable")
                    // 网络可用时触发
                    GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    // 网络丢失时触发
                    GlobalInstance.networkConnected.postValue(false)
                }
}

总结,就是通过context调用getSystemService()拿到ConnectivityManager,然后通过ConnectivityManager注册registerNetworkCallback网络监听,这里addCapability就是监听的类型。
我这里的实现是通过livedata将消息通知给观察者去进行具体业务或UI上处理。
当然这里我们还不能得到具体网络类型。

获取具体网络类型

首先定义一个泛型来描述网络类型

    enum class NetworkType{
        UNKNOWN,
        ETHERNET,
        WIFI,
    }`

然后是如何得到具体网络类型

    fun getNetworkType():NetworkType{
        val cm = (Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager)
        val caps = cm.getNetworkCapabilities(cm.activeNetwork)
        when {
            caps?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true ->{
                logger.i("getNetworkType: " + NetworkType.ETHERNET)
                return NetworkType.ETHERNET
            }
            caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true ->{
                logger.i("getNetworkType: " + NetworkType.WIFI)
                return NetworkType.WIFI
            }
            else ->{
                logger.i("getNetworkType: " + NetworkType.UNKNOWN)
                return NetworkType.UNKNOWN
            }
        }
    }

可以看到这里的返回类型就是上面定义的枚举类型,这里为什么要费力去定义枚举呢,一个是增强可读性,想想如果返回0 1 2这样的int型,以后维护很难理解对应上各自代表什么,二来存在被随意篡改的可能性(防友军误伤),换个人来维护,他可能给你返回6789之类,根本不可控。
说回到代码功能,这个也是通过ConnectivityManager获取到的,但是这个不是回调,是"随叫随应"的,需要自己控制调用时机。

设备背景

这个项目是嵌入式设备,什么意思呢,是一个Android终端,有网口可以接网线,同时有wifi芯片可以连接wifi,网络这一块对比我们手机多了一个有线网口少了蜂窝网。

问题一

我们按照前面的方案及实现设计流程为:监听到网络连上后(触发onAvailable),通过getNetworkType()方法获取到网络类型,然后展示对应网络类型图标,在监听到网络断开(触发onLost)时,隐藏网络图标。
但在实际测试过程中,经常出现网络切换了(比如wifi切换到有线),但是网络图标还是有线。
我通过日志分析,发现在网络快速切换过程中,有时是不会触发onLost的,那么我们通过livedata.postValue(true)两次post相同值,观察方不会知道我们网络类型是有变化的,结果就是不会触发去刷新网络图标状态。
这里我查询资料发现这里的网络回调接口还有一个方法onCapabilitiesChanged可以利用,它是监听网络能力变化的,但是它需要我们明确要监听的网络能力类型,如何明确?
前面我们提到了addCapability,查资料得知我们还可以通过addTransportType来添加要监听的类型,那么就变成了

    fun registerNetworkListener(){
        (Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).apply {
            val request = NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
                .build()
            registerNetworkCallback(request, object: ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    logger.i("onAvailable")
                    // 网络可用时触发
                    GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    // 网络丢失时触发
                    GlobalInstance.networkConnected.postValue(false)
                }
                override fun onCapabilitiesChanged(
                    network: Network,
                    networkCapabilities: NetworkCapabilities
                ) {
                    logger.i("onCapabilitiesChanged")
                    if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {   
                             logger.i("onCapabilitiesChanged: wifi")
                            GlobalInstance.networkConnected.postValue(true)
                       
                    } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
                        logger.i("onCapabilitiesChanged: eth")
                            GlobalInstance.networkConnected.postValue(true)
                    }
                }
}

问题二

当我们通过观察者(这里是GlobalInstance.networkConnected)监听到了网络变化,立马通过getNetworkType()去获取网络类型,有时后会拿到非实际网络类型值,既不是wifi也不是eth有线网络,我们看getNetworkType()实现可知,除开这两种类型会返回NetworkType.UNKNOWN,但是有时又是能拿到正确网络类型的。
当时项目总工让我进行延时获取,但是如果我们在getNetworkType()加延时,代码实现很别扭,调用者可能在主线程,这样可能就会阻塞住主线程(这是大忌)。所以我决定在通知状态切换时进行延时,那么就变成

    fun registerNetworkListener(){
        (Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).apply {
            val request = NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
                .build()
            registerNetworkCallback(request, object: ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    logger.i("onAvailable")
                    CoroutineUtility.ioScope.launch {
                        delay(300)
                        // 网络可用时触发
                        GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    CoroutineUtility.ioScope.launch {
                        delay(300)
                        // 网络丢失时触发
                        GlobalInstance.networkConnected.postValue(false)
                    }
                }

                override fun onCapabilitiesChanged(
                    network: Network,
                    networkCapabilities: NetworkCapabilities
                ) {
                    logger.i("onCapabilitiesChanged")
                    if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                        logger.i("onCapabilitiesChanged: wifi")
                        CoroutineUtility.ioScope.launch {
                            delay(300)
                            GlobalInstance.networkConnected.postValue(true)
                        }
                    } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
                        logger.i("onCapabilitiesChanged: eth")
                        CoroutineUtility.ioScope.launch {
                            delay(300)
                            GlobalInstance.networkConnected.postValue(true)
                        }
                    }
                }
            })
        }
    }

这里CoroutineUtility是一个协程工具类,不用关注实现,就是用协程实现延时功能(不得不说比写线程延时或者Handler方便),在状态通知时延时,而不是在getNetworkType()中延时。

结语

当时做这个项目很着急,很多问题都没来得及仔细分析和整理,如果有充足时间,其实解决这些疑难杂症还是挺有成就感的。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容