You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android网络状态监听优化:应用首次打开有网时不弹出Snackbar提示

问题根因

当前代码的问题在于:

  • LiveData会在首次观察者绑定后,把当前存储的状态回传给观察者,不管是不是首次启动,都会触发Snackbar弹出
  • 没有区分「首次初始化的状态上报」和「后续网络变化的状态上报」两种场景

具体优化方案

1. (可选)修改NetworkStatusHelper,增加初始状态主动查询

这一步可以避免首次启动时等待网络回调延迟返回状态,提升体验:

class NetworkStatusHelper(private val context: Context): LiveData<NetworkStatus>() {
    // 原有变量保持不变
    var connectivityManager: ConnectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback
    val validNetworkConnections: ArrayList<Network> = ArrayList()


    override fun onActive() {
        super.onActive()
        // 新增:主动查询一次当前网络状态,避免等待回调延迟返回初始值
        val currentNetworkAvailable = checkCurrentNetworkAvailable()
        if (currentNetworkAvailable) {
            postValue(NetworkStatus.Available)
        } else {
            postValue(NetworkStatus.Unavailable)
        }

        // 原有注册回调逻辑保持不变
        connectivityManagerCallback = getConnectivityCallbacks()
        val networkRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            NetworkRequest
                .Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .build()
        } else {
            TODO("VERSION.SDK_INT < LOLLIPOP")
        }
        connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
    }

    // 新增:同步检查当前网络是否可用的工具方法
    private fun checkCurrentNetworkAvailable(): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val activeNetwork = connectivityManager.activeNetwork ?: return false
            val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
            return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 
                    && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
        } else {
            @Suppress("DEPRECATION")
            val activeNetworkInfo = connectivityManager.activeNetworkInfo ?: return false
            @Suppress("DEPRECATION")
            return activeNetworkInfo.isConnected
        }
    }

    // 其余原有代码保持不变
    fun getConnectivityCallbacks() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                super.onAvailable(network)
                val networkCapability =
                    connectivityManager.getNetworkCapabilities(network)
                val hasNetworkConnection =
                    networkCapability?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                        ?: false

                if (hasNetworkConnection) {
                    determineInternetAccess(network)
                }
            }

            override fun onLost(network: Network) {
                super.onLost(network)
                validNetworkConnections.remove(network)
                announceNetworkStatus()
            }

            private fun determineInternetAccess(network: Network) {
                
                CoroutineScope(Dispatchers.IO).launch {
                    if (InternetAvailability.check()) {
                        withContext(Dispatchers.Main) {
                            validNetworkConnections.add(network)
                            
                            announceNetworkStatus()
                        }
                    }
                }
            }


            fun announceNetworkStatus() {
                
                if (validNetworkConnections.isNotEmpty()) {
                    postValue(NetworkStatus.Available)
                } else {
                    postValue(NetworkStatus.Unavailable)
                }
            }

        }
    } else {
        TODO("VERSION.SDK_INT < LOLLIPOP")
    }


    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
        }
    }

    object InternetAvailability {

        fun check() : Boolean {
            return try {
                val socket = Socket()
                socket.connect(InetSocketAddress("8.8.8.8",53))
                socket.close()
                true
            } catch ( e: Exception){
                e.printStackTrace()
                false
            }
        }

    }
}

2. 修改MainActivity的Observer逻辑,区分首次状态

仅需增加一个首次标记位即可实现需求逻辑:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding=DataBindingUtil.setContentView(this,R.layout.activity_main)
    // 新增:标记是否是首次接收网络状态
    var isFirstNetworkStatus = true

    NetworkStatusHelper(this@MainActivity).observe(this, Observer {
        when(it){
            NetworkStatus.Available-> {
                // 仅非首次的可用状态才弹出提示
                if (!isFirstNetworkStatus) {
                    Snackbar.make(binding.root, "Back online", Snackbar.LENGTH_LONG).show()
                }
            }
            NetworkStatus.Unavailable-> {
                // 无论是否首次,网络不可用都弹出提示
                Snackbar.make(binding.root, "No Internet connection", Snackbar.LENGTH_LONG).show()
            }
        }
        // 首次状态处理完成后修改标记,后续都走正常触发逻辑
        isFirstNetworkStatus = false
    })
}

效果验证

修改后完全符合需求:

  • 首次打开网络可用:首次状态回调触发时标记位为true,可用分支不弹Snackbar,标记位修改为false
  • 首次打开网络不可用:不可用分支不受标记位限制,直接弹出提示
  • 后续网络状态切换:标记位已经为false,两种状态变化都会触发对应Snackbar提示

内容的提问来源于stack exchange,提问作者DroidBee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 22:36:10