最近做了一個(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í)解決這些疑難雜癥還是挺有成就感的。