Android网络变化的监听

301 阅读3分钟

一、粗略版本的网络监听

这是一个粗略版本的网络监听,能满足实时性不是特别高的需求,如果要做到实时性特别高例如连上WIFI热点后一直判断热点是否有网,那么难免需要定时去ping或做一些其他动作来支持。代码主要思路是继承LiveData实现的,代码如下:

import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import androidx.lifecycle.LiveData

/**
 * 网络变化的监听
 * 连上网络后会ping一次公共CDN确认有无网络
 * 连上网络就真假WIFI变化,这个类没有实现,因为如果要做的话需要连上网络一直ping,代价有点大
 */
class NetworkConnection(context: Context) : LiveData<Boolean>() {

    private val connectivityManager: ConnectivityManager = context.getSystemService(
        Context.CONNECTIVITY_SERVICE
    ) as ConnectivityManager

    private val networkConnectionCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            checkInternetConnection()
        }

        override fun onLost(network: Network) {
            postValue(false)
        }
        
        override fun onCapabilitiesChanged(
            network: Network,
            capabilities: NetworkCapabilities
        ) {
            updateNetworkConnection(capabilities)
        }
    }

    override fun onActive() {
        super.onActive()
        updateNetworkConnection()
        try {
            connectivityManager.registerDefaultNetworkCallback(networkConnectionCallback)
        } catch (e: Exception) {
            Log.e(e,"NetworkConnection register callback failed")
        }
    }

    override fun onInactive() {
        super.onInactive()
        try {
            connectivityManager.unregisterNetworkCallback(networkConnectionCallback)
        } catch (e: Exception) {
            // ignore
        }
    }

    private fun updateNetworkConnection(capabilities: NetworkCapabilities? = null) {
        val realCapabilities = capabilities ?: connectivityManager.getNetworkCapabilities(
            connectivityManager.activeNetwork
        )
        val isConnected = realCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
        postValue(isConnected)
    }

    private fun checkInternetConnection() {
        //ping Google CDN(国内要注意不能用)
        val command = arrayOf("/system/bin/ping", "-c", "1", "8.8.8.8")
        val isConnected = try {
            val process = Runtime.getRuntime().exec(command)
            val result = process.waitFor() == 0
            process.inputStream.close()
            process.errorStream.close()
            true
        } catch (e: Exception) {
            false
        }
        postValue(isConnected)
    }
}

项目中使用很简单,如下:

NetworkConnection(requireContext()).observe(viewLifecycleOwner) {
    //UI层面的处理
    binding.llNetworkError.isGone = it
    //如果之前的网络状态是断开,那么网络恢复后是否需要刷新一次数据
    if (!mLastNetWorkState && it) {
        binding.swipeRefresh.isRefreshing = true
        //请求一次新数据
        requestData()
    }
    mLastNetWorkState = it
}

基于LiveData绑定页面周期的特性自动注册或反注册网络的监听,并且回到前台会重新判断一次网络,日常用基本能满足需求。

二、实时检测的网络状态监听

有些时候需要实时的网络检测,因为可能涉及MQTT、Sockect等的重连,下面是一个示例:

class NetworkConnection(context: Context) {

    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    /** 定义事件类型 */
    private sealed class NetworkEvent {
        data object MaybeChanged : NetworkEvent()  // 网络可能变化,需要 ping
        data object Lost : NetworkEvent()          // 网络明确断开,不用 ping
    }

    // Flow 用于触发网络状态变化
    private val _networkEvents = MutableSharedFlow<NetworkEvent>(extraBufferCapacity = 1)

    /**
     * 对外暴露 LiveData
     */
    @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
    val connectedLiveData: LiveData<Boolean> = _networkEvents
        .debounce { event -> if (event is NetworkEvent.MaybeChanged) 300 else 0 } // 防抖,只对可能变化的事件
        .mapLatest { event ->
            when (event) {
                is NetworkEvent.MaybeChanged -> checkInternetConnection()
                is NetworkEvent.Lost -> false
            }
        }
        //.distinctUntilChanged()  //根据实际情况是否去重
        .asLiveData()

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            triggerNetworkEvent(NetworkEvent.MaybeChanged)
        }

        override fun onLost(network: Network) {
            triggerNetworkEvent(NetworkEvent.Lost)
        }

        override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) {
            triggerNetworkEvent(NetworkEvent.MaybeChanged)
        }

        override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
            triggerNetworkEvent(NetworkEvent.MaybeChanged)
        }
    }

    init {
        try {
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        } catch (_: Exception) {
            //ignore
        }
        // 初始检测
        triggerNetworkEvent(NetworkEvent.MaybeChanged)
    }

    private fun triggerNetworkEvent(event: NetworkEvent) {
        scope.launch { _networkEvents.emit(event) }
    }

    /**
     * 真实网络检测函数,通过PING
     */
    private suspend fun checkInternetConnection(): Boolean = withContext(Dispatchers.IO) {
        try {
            NetworkUtil.pingRobust()
        } catch (_: IOException) {
            false
        }
    }

    /**
     * 周期性检测网络(兜底),默认间隔5秒
     */
    fun startPeriodicCheck(intervalMs: Long = 5000L) {
        scope.launch {
            while (isActive) {
                _networkEvents.emit(NetworkEvent.MaybeChanged)
                delay(intervalMs)
            }
        }
    }

    fun stop() {
        try {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        } catch (_: Exception) {
        }
        scope.cancel()
    }
}

ping的代码:

/**
 * ping google CDN check network.最多ping 3次以提高ping的健壮性
 * 放在子线程
 */
suspend fun pingRobust(retries: Int = 3, timeoutMs: Long = 1000): Boolean {
    repeat(retries) {
        if (pingOnce(timeoutMs)) return true
    }
    return false
}

private suspend fun pingOnce(timeoutMs: Long): Boolean = withContext(Dispatchers.IO) {
    try {
        val process = Runtime.getRuntime().exec(arrayOf("/system/bin/ping", "-c", "1", "-W", "${timeoutMs / 1000}", "8.8.8.8"))
        val result = process.waitFor() == 0
        process.inputStream.close()
        process.errorStream.close()
        result
    } catch (_: Exception) {
        false
    }
}

实际使用:

//订阅监听网络的变化,根据需求选择observer或observeForever
networkConnection?.connectedLiveData?.observeForever { isNetworkAvailable ->
    //do something
}
//开始周期性检测网络的变化
networkConnection?.startPeriodicCheck()