Android 监听网络状态变化(无切换中间态版)

需求:

  • 获取当前的网络状态与类型(WIFI、数据流量)
  • 监听网络状态真正变化
  • 监听网络类型发生变化

业务场景:

  • 用户打开 App 时、使用过程中,出现无网络时,显示 Toast 提示。但当 wifi、数据流量 互相切换的过程中不要有提示。
  • 下载功能支持检测到用户连接上 wifi 时开启静默下载,当换成 数据流量 时停止静默下载。

需求分析:

获取当前网络状态与类型

即提供两个方法,用于获取当前的网络状态、网络类型。

监听网络状态真正变化

网络状态真正变化,首先明确网络状态只有【有网】与【无网】,所以当 WIFI 和 数据流量 同时开启的情况下,仅关闭一项,不会提示网络状态的变更。

监听网络类型发生变化

网络类型发生变化,是指当前使用的网络类型发生变化。举个例子,WIFI、数据流量同时开启,理论上当前网络类型是 WIFI,所以当仅关闭 数据流量 时,不会提示网络类型变更,但仅关闭 WIFI 时,会提示网络类型变更为 数据流量。
这里还需要注意一点,有一个“系统默认网络”的概念:系统通常首选不按流量计费的网络而非按流量计费的网络,首选网速较快的网络而非网速较慢的网络。

技术实现:

常见监听网络状态有三种方式:

  • 监听广播
  • ConnectivityManager#registerDefaultNetworkCallback()
  • ConnectivityManager#registerNetworkCallback()

下面逐一实现看效果:

监听网络状态变化广播

 

kotlin

复制代码

class NetConnectReceiver: BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { Log.e("qingshan", "网络状态改变") context?.getSystemService(ConnectivityManager::class.java)?.allNetworkInfo?.filter { it.typeName == "MOBILE" || it.typeName == "WIFI" }?.forEach {networkInfo -> Log.e("qingshan", "${networkInfo?.typeName}, isConnect= ${networkInfo?.isConnected}") } } } //动态注册广播监听 class CustomApplication: Application() { override fun onCreate() { super.onCreate() //这里模拟工具类场景:全局一个监听,然后在工具类中分发。 registerReceiver(NetConnectReceiver(), IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) } }

效果:

  • 数据流量、wifi 全关,冷启动。收到两次广播,均表示【数据流量、wifi 】无连接。
 

kotlin

复制代码

2023-11-10 14:21:58.081 13917-13917 qingshan E 网络状态改变 2023-11-10 14:21:58.082 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:21:58.082 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:21:58.083 13917-13917 qingshan E 网络状态改变 2023-11-10 14:21:58.083 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:21:58.083 13917-13917 qingshan E WIFI, isConnect= false

  • 数据流量、wifi 全开,冷启动。收到连续两次广播,均表示当前【WIFI】连接。
 

kotlin

复制代码

2023-11-10 14:13:46.002 13917-13917 qingshan E 网络状态改变 2023-11-10 14:13:46.002 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:13:46.002 13917-13917 qingshan E WIFI, isConnect= true 2023-11-10 14:13:46.003 13917-13917 qingshan E 网络状态改变 2023-11-10 14:13:46.003 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:13:46.003 13917-13917 qingshan E WIFI, isConnect= true

  • 数据流量、wifi 全开,仅关闭流量。无广播。
  • 数据流量、wifi 全开,仅关闭wifi(用于模拟断开wifi 或 wifi网络不佳时系统自动启用数据流量)。收到多次广播,有时表示先【数据流量、wifi 】无连接,然后又出现【数据流量】连接,有时均表示当前【数据流量】连接。
 

kotlin

复制代码

2023-11-10 14:18:46.622 13917-13917 qingshan E 网络状态改变 2023-11-10 14:18:46.624 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:18:46.624 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:18:46.624 13917-13917 qingshan E 网络状态改变 2023-11-10 14:18:46.624 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:18:46.624 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:18:46.665 13917-13917 qingshan E 网络状态改变 2023-11-10 14:18:46.666 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:18:46.666 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:18:46.666 13917-13917 qingshan E 网络状态改变 2023-11-10 14:18:46.666 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:18:46.666 13917-13917 qingshan E WIFI, isConnect= false

有时日志如下:

 

kotlin

复制代码

2023-11-10 14:25:03.460 13917-13917 qingshan E 网络状态改变 2023-11-10 14:25:03.461 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:25:03.461 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:25:03.461 13917-13917 qingshan E 网络状态改变 2023-11-10 14:25:03.462 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:25:03.462 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:25:03.527 13917-13917 qingshan E 网络状态改变 2023-11-10 14:25:03.528 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:25:03.528 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:25:03.528 13917-13917 qingshan E 网络状态改变 2023-11-10 14:25:03.528 13917-13917 qingshan E MOBILE, isConnect= true 2023-11-10 14:25:03.528 13917-13917 qingshan E WIFI, isConnect= false

  • 数据流量、wifi 全关。收到两次广播,均表示【数据流量、wifi 】无连接。

注意:先关 wifi 再关数据时,先打印上面(“数据流量、wifi 全开,仅关闭wifi”场景)日志,再打印下面日志。

 

kotlin

复制代码

2023-11-10 14:21:58.081 13917-13917 qingshan E 网络状态改变 2023-11-10 14:21:58.082 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:21:58.082 13917-13917 qingshan E WIFI, isConnect= false 2023-11-10 14:21:58.083 13917-13917 qingshan E 网络状态改变 2023-11-10 14:21:58.083 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:21:58.083 13917-13917 qingshan E WIFI, isConnect= false

  • 仅开数据流量,再开 wifi(用于模拟使用过程中自动连接上wifi)。收到多次广播,均表示【wifi】连接。
 

kotlin

复制代码

2023-11-10 14:31:21.285 13917-13917 qingshan E 网络状态改变 2023-11-10 14:31:21.286 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:31:21.286 13917-13917 qingshan E WIFI, isConnect= true 2023-11-10 14:31:21.287 13917-13917 qingshan E 网络状态改变 2023-11-10 14:31:21.287 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:31:21.287 13917-13917 qingshan E WIFI, isConnect= true 2023-11-10 14:31:21.317 13917-13917 qingshan E 网络状态改变 2023-11-10 14:31:21.319 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:31:21.319 13917-13917 qingshan E WIFI, isConnect= true 2023-11-10 14:31:21.319 13917-13917 qingshan E 网络状态改变 2023-11-10 14:31:21.320 13917-13917 qingshan E MOBILE, isConnect= false 2023-11-10 14:31:21.320 13917-13917 qingshan E WIFI, isConnect= true

ConnectivityManager#registerDefaultNetworkCallback()

要求 android sdk >=24 用于监听“系统默认网络”发生变更。 请勿在回调中调用 ConnectivityManager 的同步方法来查找新可用网络的属性,因为这会受到竞态条件的影响。例如:在 onLost() 回调中调用 connectivityManager.getNetworkCapabilities()。

 

kotlin

复制代码

class CustomApplication: Application() { override fun onCreate() { super.onCreate() //这里模拟工具类场景:全局一个监听,然后在工具类中分发。 getSystemService(ConnectivityManager::class.java).apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { registerDefaultNetworkCallback(object : NetworkCallback() { override fun onAvailable(network: Network) { super.onAvailable(network) Log.e("qingshan", "def -> onAvailable") } override fun onLost(network: Network) { super.onLost(network) Log.e("qingshan", "def -> onLost") } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { super.onCapabilitiesChanged(network, networkCapabilities) Log.e("qingshan", "def -> 可正常访问网络 = ${networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)} " + "& 数据连接 = ${networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)} " + "& wifi连接= ${networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)}") } }) } } } }

效果

  • 数据流量、wifi 全关,冷启动。无回调触发。
  • 数据流量、wifi 全开,冷启动。触发 onAvailable() 和 onCapabilitiesChanged() 回调。表示当前【WIFI】连接。
 

kotlin

复制代码

2023-11-10 15:22:31.427 8342-12273 qingshan E def -> onAvailable 2023-11-10 15:22:31.427 8342-12273 qingshan E def -> 可正常访问网络 = true & 数据连接 = false & wifi连接= true

  • 数据流量、wifi 全开,仅关闭流量。无回调触发。
  • 数据流量、wifi 全开,仅关闭wifi(用于模拟断开wifi 或 wifi网络不佳时系统自动启用数据流量)。触发一次 onLost() 和 onAvailable() 回调,触发多次 onCapabilitiesChanged() 回调。表示当前【数据流量】连接。
 

kotlin

复制代码

2023-11-10 15:30:26.103 8342-12273 qingshan E def -> onLost 2023-11-10 15:30:26.150 8342-12273 qingshan E def -> onAvailable 2023-11-10 15:30:26.151 8342-12273 qingshan E def -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false

  • 数据流量、wifi 全关。数据流量和 wifi 谁先关闭触发的回调不同,wifi 先关会触发两次 onLost() 回调。有时表示【数据流量、wifi】无连接,有时先表示【wifi】无连接,切换为【数据流量】连接,再表示【数据流量、wifi】无连接。

注意:先关 wifi 再关数据时,先打印上面(“数据流量、wifi 全开,仅关闭wifi”场景)日志,再打印下面日志。

 

kotlin

复制代码

2023-11-10 15:35:42.923 8342-12273 qingshan E def -> onLost

  • 仅开数据流量,再开 wifi(用于模拟使用过程中自动连接上wifi)。触发一次 onAvailable() 回调,触发多次 onCapabilitiesChanged() 回调。表示当前【wifi】连接,但有时会有不同的网络状态。
 

kotlin

复制代码

2023-11-10 15:33:05.596 8342-12273 qingshan E def -> onAvailable 2023-11-10 15:33:05.596 8342-12273 qingshan E def -> 可正常访问网络 = true & 数据连接 = false & wifi连接= true

有时日志如下:

 

kotlin

复制代码

2023-11-10 15:39:22.644 8342-12273 qingshan E def -> onAvailable 2023-11-10 15:39:22.645 8342-12273 qingshan E def -> 可正常访问网络 = false & 数据连接 = true & wifi连接= false 2023-11-10 15:39:22.842 8342-12273 qingshan E def -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false

ConnectivityManager#registerNetworkCallback()

用于监听所有网络发生变更。 请勿在回调中调用 ConnectivityManager 的同步方法来查找新可用网络的属性,因为这会受到竞态条件的影响。例如:在 onLost() 回调中调用 connectivityManager.getNetworkCapabilities()。

 

kotlin

复制代码

class CustomApplication: Application() { override fun onCreate() { super.onCreate() //这里模拟工具类场景:全局一个监听,然后在工具类中分发。 getSystemService(ConnectivityManager::class.java).apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { registerNetworkCallback(NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build(), object : NetworkCallback(){ override fun onAvailable(network: Network) { super.onAvailable(network) Log.e("qingshan", "all -> onAvailable") } override fun onLost(network: Network) { super.onLost(network) Log.e("qingshan", "all -> onLost") } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { super.onCapabilitiesChanged(network, networkCapabilities) Log.e("qingshan", "all -> 可正常访问网络 = ${networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)} " + "& 数据连接 = ${networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)} " + "& wifi连接= ${networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)}") } }) } } } }

效果

  • 数据流量、wifi 全关,冷启动。无回调触发。
  • 数据流量、wifi 全开,冷启动。触发两次 onAvailable() 和 onCapabilitiesChanged() 回调,表示当前【数据流量、wifi】均连接。
 

kotlin

复制代码

2023-11-10 17:24:37.153 10713-6397 qingshan E all -> onAvailable 2023-11-10 17:24:37.153 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = false & wifi连接= true 2023-11-10 17:24:37.155 10713-6397 qingshan E all -> onAvailable 2023-11-10 17:24:37.155 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false

  • 数据流量、wifi 全开,仅关闭流量。触发一次 onLost() 回调,表示当前有网络关闭,具体是什么不知道。
 

kotlin

复制代码

2023-11-10 17:26:28.243 10713-6397 qingshan E all -> onLost

  • 数据流量、wifi 全开,仅关闭wifi(用于模拟断开wifi 或 wifi网络不佳时系统自动启用数据流量)。触发一次 onLoast() 回调,表示当前有网络关闭。因为关闭的是“系统默认网络”,所以会触发多次 onCapabilitiesChanged() 回调,表示当前变成【数据流量】连接。
 

kotlin

复制代码

2023-11-10 17:30:43.302 10713-6397 qingshan E all -> onLost 2023-11-10 17:30:45.464 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false

  • 数据流量、wifi 全关。触发两次 onLost() 回调,但先 wifi 时会在两次 onLost() 中间触发一次 onCapabilitiesChanged() 回调。表示当前网络逐个不可用。

先关wifi,再关数据流量,日志如下:

 

kotlin

复制代码

2023-11-10 17:36:19.335 10713-6397 qingshan E all -> onLost 2023-11-10 17:36:19.475 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false 2023-11-10 17:36:20.802 10713-6397 qingshan E all -> onLost

先关数据流量,再关wifi,日志如下:

 

kotlin

复制代码

2023-11-10 17:37:20.487 10713-6397 qingshan E all -> onLost 2023-11-10 17:37:22.176 10713-6397 qingshan E all -> onLost

  • 仅开数据流量,再开 wifi(用于模拟使用过程中自动连接上wifi)。会触发一次 onAvailable(),触发多次 onCapabilitiesChanged() 回调。表示当前【wifi】已连接(但不意味着正在使用的是 wifi,具体正在使用的网络类型由系统决定,即系统默认网络)。
 

kotlin

复制代码

2023-11-10 17:33:42.284 10713-6397 qingshan E all -> onAvailable 2023-11-10 17:33:42.284 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = false & wifi连接= true

  • 仅开wifi,再开数据流量。会触发一次 onAvailable(),触发多次 onCapabilitiesChanged() 回调。表示当前【数据流量】已连接(但不意味着正在使用的是数据流量,具体正在使用的网络类型由系统决定,即系统默认网络)。
 

kotlin

复制代码

2023-11-10 17:27:49.875 10713-6397 qingshan E all -> onAvailable 2023-11-10 17:27:49.876 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false 2023-11-10 17:27:49.878 10713-6397 qingshan E all -> 可正常访问网络 = true & 数据连接 = true & wifi连接= false

总结

根据上面的实际运行效果可以得知:

  • ConnectivityManager#registerNetworkCallback() 是监听所有网络变换的,监听范围广,但无法得知当前“系统默认网络”是什么,可以实现判断网络状态,但无法判断网络类型。
  • 广播监听 与 ConnectivityManager#registerDefaultNetworkCallback() 都是监听“系统默认网络”,所以可以实现网络状态与类型的判断,但都存在重复回调的情况,所以要做过滤处理,以及“系统默认网络”切换到普通网络时会有偶现短暂“无网络”状态,需要做延迟处理。
  • 广播监听所使用的方式有标记为“废弃”,同时 ConnectivityManager#registerDefaultNetworkCallback() 也有版本的限制,所以可以两者结合使用,优先使用 egisterDefaultNetworkCallback(),广播监听用于兜底。

所以,综合上述,得出如下工具类实现:

 

kotlin

复制代码

import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.os.Build import android.os.Handler import android.os.Looper sealed class ConnectType(val value: Int) { object Mobile : ConnectType(0) object Wifi : ConnectType(1) object None : ConnectType(-1) companion object { fun convert2Type(value: Int): ConnectType { return when (value) { Mobile.value -> Mobile Wifi.value -> Wifi else -> None } } } } object NetConnectManager { private var mConnectivityManager: ConnectivityManager? = null private val mainHandler = Handler(Looper.getMainLooper()) private val mNetTypeListener = mutableListOf<(type: ConnectType) -> Unit>() private val mNetStateListener = mutableListOf<(isAvailable: Boolean) -> Unit>() private var mCurrentConnectType: ConnectType? = null private var mIsNetAvailable: Boolean? = null /** * 初始化 */ fun init(context: Context) { mConnectivityManager = context.getSystemService(ConnectivityManager::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mConnectivityManager?.registerDefaultNetworkCallback(DefaultNetConnectCallback()) } else { context.registerReceiver( NetConnectReceiver(), IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) ) } } /** * 注册网络类型监听 */ fun addNetTypeChangeListener(listener: (type: ConnectType) -> Unit) { mNetTypeListener.add(listener) } /** * 反注册网络类型监听 */ fun removeNetTypeChangeListener(listener: (type: ConnectType) -> Unit) { mNetTypeListener.remove(listener) } /** * 注册网络状态监听 */ fun addNetStatusChangeListener(listener: (isAvailable: Boolean) -> Unit) { mNetStateListener.add(listener) } /** * 反注册网络状态监听 */ fun removeNetStatusChangeListener(listener: (isAvailable: Boolean) -> Unit) { mNetStateListener.remove(listener) } /** * 获取当前网络类型 */ fun getConnectType(): ConnectType { if (mConnectivityManager == null) { throw UninitializedPropertyAccessException("请先调用init()初始化") } return mCurrentConnectType ?: mConnectivityManager?.getNetworkCapabilities( mConnectivityManager?.activeNetwork ).let { return if (it?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true) { ConnectType.Mobile } else if (it?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { ConnectType.Wifi } else { ConnectType.None } } } /** * 获取当前是否网络已连接 */ fun isConnected(): Boolean { if (mConnectivityManager == null) { throw UninitializedPropertyAccessException("请先调用init()初始化") } return (mIsNetAvailable ?: mConnectivityManager?.getNetworkCapabilities(mConnectivityManager?.activeNetwork) ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) == true } private class DefaultNetConnectCallback : ConnectivityManager.NetworkCallback() { override fun onLost(network: Network) { super.onLost(network) mCurrentConnectType = ConnectType.None mainHandler.postDelayed({ if (mCurrentConnectType == ConnectType.None && mIsNetAvailable == true) { mIsNetAvailable = false mNetStateListener.forEach { it.invoke(false) } mNetTypeListener.forEach { it(ConnectType.None) } } }, 500) } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { super.onCapabilitiesChanged(network, networkCapabilities) mainHandler.post { val isConnected = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) val isCellular = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) val isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) if (isConnected) { val newConnectType = if (isCellular) ConnectType.Mobile else if (isWifi) ConnectType.Wifi else ConnectType.None if (mIsNetAvailable == null || mIsNetAvailable == false) { mIsNetAvailable = true mNetStateListener.forEach { it(true) } } if (mCurrentConnectType != newConnectType) { mCurrentConnectType = newConnectType mNetTypeListener.forEach { it(newConnectType) } } } } } } private class NetConnectReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val activityNetworkInfo = context?.getSystemService(ConnectivityManager::class.java)?.allNetworkInfo?.filter { (it.type == ConnectType.Mobile.value || it.type == ConnectType.Wifi.value) && it.isConnected }?.firstOrNull() if (activityNetworkInfo != null) { if (mIsNetAvailable == null || mIsNetAvailable == false) { mIsNetAvailable = true mNetStateListener.forEach { it(true) } } ConnectType.convert2Type(activityNetworkInfo.type).let { connectType -> if (connectType != mCurrentConnectType) { mCurrentConnectType = connectType mNetTypeListener.forEach { it(connectType) } } } return } mCurrentConnectType = ConnectType.None mainHandler.postDelayed({ if (mCurrentConnectType == ConnectType.None && mIsNetAvailable == true) { mIsNetAvailable = false mNetStateListener.forEach { it(false) } mNetTypeListener.forEach { it(ConnectType.None) } } }, 500) } } }

效果:

测试代码:

 

kotlin

复制代码

class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.e("qingshan", "网络是否连接= ${NetConnectManager.isConnected()} & 网络类型= ${NetConnectManager.getConnectType()}") NetConnectManager.addNetTypeChangeListener { Log.e("qingshan", "网络类型[监听]= $it") } NetConnectManager.addNetStatusChangeListener { Log.e("qingshan", "网络是否已连接[监听]= $it") } } }

  • 数据流量、wifi 全关,冷启动。
 

kotlin

复制代码

qingshan E 网络是否连接= false & 网络类型= com.stefan.simpleskin.ConnectType$None@6f21c25

  • 数据流量、wifi 全开,冷启动。
 

kotlin

复制代码

qingshan E 网络是否连接= true & 网络类型= com.stefan.simpleskin.ConnectType$Wifi@34771fa qingshan E 网络类型[监听]= com.stefan.simpleskin.ConnectType$Wifi@34771fa

  • 数据流量、wifi 全开,仅关闭流量。 无监听回调。
  • 数据流量、wifi 全开,仅关闭wifi(用于模拟断开wifi 或 wifi网络不佳时系统自动启用数据流量)。
 

kotlin

复制代码

qingshan E 网络类型[监听]= com.stefan.simpleskin.ConnectType$Mobile@cf38ed1

  • 数据流量、wifi 全关。
 

kotlin

复制代码

qingshan E 网络是否已连接[监听]= false qingshan E 网络类型[监听]= com.stefan.simpleskin.ConnectType$None@fddad36

  • 仅开数据流量,再开 wifi(用于模拟使用过程中自动连接上wifi)。
 

kotlin

复制代码

qingshan E 网络类型[监听]= com.stefan.simpleskin.ConnectType$Wifi@34771fa

  • 仅开wifi,再开数据流量。无监听回调。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/39936.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ppt接单渠道大公开‼️

PPT 接单主要分两种&#xff1a;PPT 模板投稿和PPT 定制接单&#xff0c;我们先从简单的 PPT 模板投稿说起。 PPT 模板投稿 利用业余时间&#xff0c;做一些 PPT 模板上传到平台&#xff0c;只要有人下载你的模板&#xff0c;你就有收入。如果模板质量高&#xff0c;简直就是一…

【设计模式】观察者模式(定义 | 特点 | Demo入门讲解)

文章目录 定义结构Demo | 代码Subject目标类Observer抽象观察者观察者1 | CPU监听器观察者2 | 内存监听器客户端 | Client 优点适合场景 定义 所谓观察者模式就是你是被观察的那个对象&#xff0c;你爸爸妈妈就是观察者&#xff0c;一天24h盯着你&#xff0c;一旦你不听话&…

【BUUCTF-PWN】7-[第五空间2019 决赛]PWN5

参考&#xff1a;BUU pwn [第五空间2019 决赛]PWN5 //格式化字符串漏洞 - Nemuzuki - 博客园 (cnblogs.com) 格式化字符串漏洞原理详解_printf 任意内存读取-CSDN博客 32位小端排序&#xff0c;有栈溢出保护 运行效果&#xff1a; 查看main函数 存在格式化字符串漏洞 输…

SQL二次注入原理分析

二次注入在测试的时候比较少见&#xff0c;或者说很难被测出来&#xff0c;因为测的时候首先要去找注入的位置&#xff0c;其次是去判断第一次执行的SQL语句&#xff0c;然后还要去判断第二次进行调用的 SQL 语句。而关键问题就出在第二次的调用上面。 下面以一个常用过滤方法…

macos下搭建minikube dashboard的启动

背景 最近在复习一下k8s环境相关的知识&#xff0c;需要在自己电脑上搭建一个minikube的环境供自己使用。但是因为docker的镜像仓库最近被墙了&#xff0c;因此在执行minikube dashboard的时候&#xff0c;拉不到相应的镜像&#xff0c;就导致页面看不到相应的一些信息因此本文…

【PYG】dataloader和densedataloader

DenseDataLoader 是专门用于处理稠密图数据的&#xff0c;而 DataLoader 通常用于处理稀疏图数据。两者的主要区别在于它们的输入数据格式和处理方式。DenseDataLoader 适合处理固定大小的邻接矩阵和节点特征矩阵的数据&#xff0c;而 DataLoader 更加灵活&#xff0c;可以处理…

flask中解决图片不显示的问题(很细微的点)

我在编写flask项目的时候&#xff0c;在编写html的时候&#xff0c;发现不管我的图片路径如何变化&#xff0c;其就是显示不出来。如下图我框中的地方。 我尝试过使用浏览器打开&#xff0c;是可以的。 一旦运行这个flask项目&#xff0c;就无法显示了。 我查阅资料后。发现…

简易版async/await

参考&#xff1a;https://juejin.cn/post/7007031572238958629?searchId20240704101813568E9B5B1013C881A239#heading-15 总结一下async/await的知识点 1、 await只能在async函数中使用&#xff0c;不然会报错 2、 async函数返回的是一个Promise对象&#xff0c;有无值看有…

泛微开发修炼之旅--29用计划任务定时发送邮件提醒

文章链接&#xff1a;29用计划任务定时发送邮件提醒

[单master节点k8s部署]17.监控系统构建(二)Prometheus安装

prometheus server安装 创建sa账号&#xff0c;对prometheus server进行授权。因为Prometheus是安装在pod里面&#xff0c;以pod的形式去运行的&#xff0c;因此需要创建sa&#xff0c;并对他做rbac授权。 apiVersion: v1 kind: ServiceAccount metadata:name: monitornamesp…

k8s-第九节-命名空间

命名空间 如果一个集群中部署了多个应用&#xff0c;所有应用都在一起&#xff0c;就不太好管理&#xff0c;也可以导致名字冲突等。 我们可以使用 namespace 把应用划分到不同的命名空间&#xff0c;跟代码里的 namespace 是一个概念&#xff0c;只是为了划分空间。 # 创建命…

LeetCode热题100刷题4:76. 最小覆盖子串、239. 滑动窗口最大值、53. 最大子数组和、56. 合并区间

76. 最小覆盖子串 滑动窗口解决字串问题。 labuladong的算法小抄中关于滑动窗口的算法总结&#xff1a; class Solution { public:string minWindow(string s, string t) {unordered_map<char,int> need,window;for(char c : t) {need[c];}int left 0, right 0;int …

2.8亿东亚五国建筑数据分享

数据是GIS的血液&#xff01; 我们现在为你分享东亚5国的2.8亿条建筑轮廓数据&#xff0c;该数据包括中国、日本、朝鲜、韩国和蒙古5个东亚国家完整、高质量的建筑物轮廓数据&#xff0c;你可以在文末查看领取方法。 数据介绍 虽然开源的全球的建筑数据已经有微软的建筑数据…

elementUI中table组件固定列时会渲染两次模板内容问题

今天在使用elementUI的table组件时&#xff0c;由于业务需要固定表格的前几项列&#xff0c;然后获取表格对象时发现竟然有两个对象。 查阅资料发现&#xff0c;elementUI的固定列的实现原理是将两个表格拼装而成&#xff0c;因此获取的对象也是两个。对于需要使用对象的方法的…

vxe-table的序号一样

使用vxe-table的时候&#xff0c;有的时候会出现序号相同的现象&#xff0c;这种现象一般出现在我们后面自己添加的行中&#xff0c;就像这种 此时的这三个序号是相同的&#xff0c;我来说一下原因&#xff0c;这是在添加新的一行的时候&#xff0c;有的时候数据很多&#xff0…

Mac 运行 Windows 软件,Parallels Desktop 19和 CrossOver 24全面对比

Parallels Desktop 和 CrossOver 都是能满足你「在 Mac 上运行 Windows 软件」需求的工具。可能很多人都已经知道 Parallels Desktop 是「虚拟机」&#xff0c;但 CrossOver 其实并不是「虚拟机」。这两款软件有相同的作用&#xff0c;但由于实现原理的不同&#xff0c;两者也有…

系统提示我未定义与 ‘double‘ 类型的输入参数相对应的函数 ‘finverse‘,如何解决?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

Kubernetes 部署简单的应用

Kubernetes 部署简单的应用 Kubernetes 是一个强大的容器编排平台&#xff0c;它可以帮助我们自动化应用程序的部署、扩展和管理。在本期文章中&#xff0c;我们将学习如何使用 Kubernetes 部署一个简单的应用程序。 1. 环境准备 确保你已经安装了 Kubernetes 集群&#xff…

【python模块】argparse

文章目录 argparse模块介绍基本用法add_argument() argparse模块介绍 argparse 模块是 Python 标准库中的一个用于编写用户友好的命令行接口&#xff08;CLI&#xff09;的模块。它允许程序定义它所需要的命令行参数&#xff0c;然后 argparse 会自动从 sys.argv 解析出那些参…

TCP粘包解决方法

一. 产生原因及解决方法 产生原因&#xff1a;TCP是面向连接、基于字节流的协议&#xff0c;其无边界标记。当服务端处理速度比不其接收速度时&#xff0c;就很容易产生粘包现象。 解决方法&#xff1a;目前主要有两种解决方法&#xff0c;一个是在内容中添加分割标识&#xf…