如何正確監(jiān)聽(tīng)網(wǎng)絡(luò)連接斷開(kāi)、網(wǎng)絡(luò)類型

最近做了一個(gè)需求,監(jiān)聽(tīng)網(wǎng)絡(luò)連接斷開(kāi),并根據(jù)連接的網(wǎng)絡(luò)類型實(shí)時(shí)顯示對(duì)應(yīng)狀態(tài)給到用戶

監(jiān)聽(tīng)網(wǎng)絡(luò)連接或斷開(kāi)

查了下AI,給到的建議基本是如下類似實(shí)現(xiàn)

    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")
                    // 網(wǎng)絡(luò)可用時(shí)觸發(fā)
                    GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    // 網(wǎng)絡(luò)丟失時(shí)觸發(fā)
                    GlobalInstance.networkConnected.postValue(false)
                }
}

總結(jié),就是通過(guò)context調(diào)用getSystemService()拿到ConnectivityManager,然后通過(guò)ConnectivityManager注冊(cè)registerNetworkCallback網(wǎng)絡(luò)監(jiān)聽(tīng),這里addCapability就是監(jiān)聽(tīng)的類型。
我這里的實(shí)現(xiàn)是通過(guò)livedata將消息通知給觀察者去進(jìn)行具體業(yè)務(wù)或UI上處理。
當(dāng)然這里我們還不能得到具體網(wǎng)絡(luò)類型。

獲取具體網(wǎng)絡(luò)類型

首先定義一個(gè)泛型來(lái)描述網(wǎng)絡(luò)類型

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

然后是如何得到具體網(wǎng)絡(luò)類型

    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
            }
        }
    }

可以看到這里的返回類型就是上面定義的枚舉類型,這里為什么要費(fèi)力去定義枚舉呢,一個(gè)是增強(qiáng)可讀性,想想如果返回0 1 2這樣的int型,以后維護(hù)很難理解對(duì)應(yīng)上各自代表什么,二來(lái)存在被隨意篡改的可能性(防友軍誤傷),換個(gè)人來(lái)維護(hù),他可能給你返回6789之類,根本不可控。
說(shuō)回到代碼功能,這個(gè)也是通過(guò)ConnectivityManager獲取到的,但是這個(gè)不是回調(diào),是"隨叫隨應(yīng)"的,需要自己控制調(diào)用時(shí)機(jī)。

設(shè)備背景

這個(gè)項(xiàng)目是嵌入式設(shè)備,什么意思呢,是一個(gè)Android終端,有網(wǎng)口可以接網(wǎng)線,同時(shí)有wifi芯片可以連接wifi,網(wǎng)絡(luò)這一塊對(duì)比我們手機(jī)多了一個(gè)有線網(wǎng)口少了蜂窩網(wǎng)。

問(wèn)題一

我們按照前面的方案及實(shí)現(xiàn)設(shè)計(jì)流程為:監(jiān)聽(tīng)到網(wǎng)絡(luò)連上后(觸發(fā)onAvailable),通過(guò)getNetworkType()方法獲取到網(wǎng)絡(luò)類型,然后展示對(duì)應(yīng)網(wǎng)絡(luò)類型圖標(biāo),在監(jiān)聽(tīng)到網(wǎng)絡(luò)斷開(kāi)(觸發(fā)onLost)時(shí),隱藏網(wǎng)絡(luò)圖標(biāo)。
但在實(shí)際測(cè)試過(guò)程中,經(jīng)常出現(xiàn)網(wǎng)絡(luò)切換了(比如wifi切換到有線),但是網(wǎng)絡(luò)圖標(biāo)還是有線。
我通過(guò)日志分析,發(fā)現(xiàn)在網(wǎng)絡(luò)快速切換過(guò)程中,有時(shí)是不會(huì)觸發(fā)onLost的,那么我們通過(guò)livedata.postValue(true)兩次post相同值,觀察方不會(huì)知道我們網(wǎng)絡(luò)類型是有變化的,結(jié)果就是不會(huì)觸發(fā)去刷新網(wǎng)絡(luò)圖標(biāo)狀態(tài)。
這里我查詢資料發(fā)現(xiàn)這里的網(wǎng)絡(luò)回調(diào)接口還有一個(gè)方法onCapabilitiesChanged可以利用,它是監(jiān)聽(tīng)網(wǎng)絡(luò)能力變化的,但是它需要我們明確要監(jiān)聽(tīng)的網(wǎng)絡(luò)能力類型,如何明確?
前面我們提到了addCapability,查資料得知我們還可以通過(guò)addTransportType來(lái)添加要監(jiān)聽(tīng)的類型,那么就變成了

    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")
                    // 網(wǎng)絡(luò)可用時(shí)觸發(fā)
                    GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    // 網(wǎng)絡(luò)丟失時(shí)觸發(fā)
                    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)
                    }
                }
}

問(wèn)題二

當(dāng)我們通過(guò)觀察者(這里是GlobalInstance.networkConnected)監(jiān)聽(tīng)到了網(wǎng)絡(luò)變化,立馬通過(guò)getNetworkType()去獲取網(wǎng)絡(luò)類型,有時(shí)后會(huì)拿到非實(shí)際網(wǎng)絡(luò)類型值,既不是wifi也不是eth有線網(wǎng)絡(luò),我們看getNetworkType()實(shí)現(xiàn)可知,除開(kāi)這兩種類型會(huì)返回NetworkType.UNKNOWN,但是有時(shí)又是能拿到正確網(wǎng)絡(luò)類型的。
當(dāng)時(shí)項(xiàng)目總工讓我進(jìn)行延時(shí)獲取,但是如果我們?cè)趃etNetworkType()加延時(shí),代碼實(shí)現(xiàn)很別扭,調(diào)用者可能在主線程,這樣可能就會(huì)阻塞住主線程(這是大忌)。所以我決定在通知狀態(tài)切換時(shí)進(jìn)行延時(shí),那么就變成

    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)
                        // 網(wǎng)絡(luò)可用時(shí)觸發(fā)
                        GlobalInstance.networkConnected.postValue(true)
                    }

                }

                override fun onLost(network: Network) {
                    logger.i("onLost")
                    CoroutineUtility.ioScope.launch {
                        delay(300)
                        // 網(wǎng)絡(luò)丟失時(shí)觸發(fā)
                        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是一個(gè)協(xié)程工具類,不用關(guān)注實(shí)現(xiàn),就是用協(xié)程實(shí)現(xiàn)延時(shí)功能(不得不說(shuō)比寫(xiě)線程延時(shí)或者Handler方便),在狀態(tài)通知時(shí)延時(shí),而不是在getNetworkType()中延時(shí)。

結(jié)語(yǔ)

當(dāng)時(shí)做這個(gè)項(xiàng)目很著急,很多問(wèn)題都沒(méi)來(lái)得及仔細(xì)分析和整理,如果有充足時(shí)間,其實(shí)解決這些疑難雜癥還是挺有成就感的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容