小于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或更高版本的监听方式,使用注册回调的方式,代码如下:
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) }
}
}