小于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) }
- }
-
- }
-