最近做了一个需求,监听网络连接断开,并根据连接的网络类型实时显示对应状态给到用户
监听网络连接或断开
查了下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()中延时。
结语
当时做这个项目很着急,很多问题都没来得及仔细分析和整理,如果有充足时间,其实解决这些疑难杂症还是挺有成就感的。