Skip to content

网络状态监听

源:Android ConnectivityManager | iOS NWPathMonitor

监听网络连接状态是应用处理离线场景的基础。本文将展示如何跨平台监听网络状态变化。

平台差异对比

平台原生 API支持能力权限要求
AndroidConnectivityManager + NetworkCallback网络类型、是否计费、实时监听ACCESS_NETWORK_STATE
iOSNWPathMonitor网络类型、是否昂贵、实时监听无需权限
Desktopjava.net.NetworkInterface基础检测无需权限

expect/actual 实现方案

标准代码块

kotlin
enum class NetworkType {
    WIFI,
    CELLULAR,
    ETHERNET,
    NONE
}

data class NetworkStatus(
    val isConnected: Boolean,
    val type: NetworkType,
    val isMetered: Boolean = false
)

expect class NetworkMonitor {
    fun startMonitoring(callback: (NetworkStatus) -> Unit)
    fun stopMonitoring()
    fun getCurrentStatus(): NetworkStatus
}
kotlin
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest

actual class NetworkMonitor(private val context: Context) {
    
    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
        as ConnectivityManager
    private var networkCallback: ConnectivityManager.NetworkCallback? = null
    
    actual fun startMonitoring(callback: (NetworkStatus) -> Unit) {
        val request = NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()
        
        networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                callback(getCurrentStatus())
            }
            
            override fun onLost(network: Network) {
                callback(getCurrentStatus())
            }
        }
        
        connectivityManager.registerNetworkCallback(request, networkCallback!!)
    }
    
    actual fun stopMonitoring() {
        networkCallback?.let {
            connectivityManager.unregisterNetworkCallback(it)
        }
        networkCallback = null
    }
    
    actual fun getCurrentStatus(): NetworkStatus {
        val network = connectivityManager.activeNetwork
        val capabilities = connectivityManager.getNetworkCapabilities(network)
        
        if (capabilities == null) {
            return NetworkStatus(false, NetworkType.NONE)
        }
        
        val type = when {
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkType.WIFI
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkType.CELLULAR
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> NetworkType.ETHERNET
            else -> NetworkType.NONE
        }
        
        val isMetered = !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
        
        return NetworkStatus(
            isConnected = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET),
            type = type,
            isMetered = isMetered
        )
    }
}
kotlin
import platform.Network.*
import platform.Foundation.*

actual class NetworkMonitor {
    
    private var monitor: NWPathMonitor? = null
    private var currentCallback: ((NetworkStatus) -> Unit)? = null
    
    actual fun startMonitoring(callback: (NetworkStatus) -> Unit) {
        currentCallback = callback
        monitor = NWPathMonitor()
        
        monitor?.pathUpdateHandler = { path ->
            val status = pathToStatus(path)
            callback(status)
        }
        
        val queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND.toLong(), 0u)
        monitor?.start(queue = queue)
    }
    
    actual fun stopMonitoring() {
        monitor?.cancel()
        monitor = null
        currentCallback = null
    }
    
    actual fun getCurrentStatus(): NetworkStatus {
        val path = monitor?.currentPath
        return if (path != null) {
            pathToStatus(path)
        } else {
            NetworkStatus(false, NetworkType.NONE)
        }
    }
    
    private fun pathToStatus(path: NWPath): NetworkStatus {
        val isConnected = path.status == NWPathStatusSatisfied
        
        val type = when {
            path.usesInterfaceType(NWInterfaceTypeWifi) -> NetworkType.WIFI
            path.usesInterfaceType(NWInterfaceTypeCellular) -> NetworkType.CELLULAR
            path.usesInterfaceType(NWInterfaceTypeWiredEthernet) -> NetworkType.ETHERNET
            else -> NetworkType.NONE
        }
        
        return NetworkStatus(
            isConnected = isConnected,
            type = type,
            isMetered = path.isExpensive
        )
    }
}
kotlin
actual class NetworkMonitor {
    
    actual fun startMonitoring(callback: (NetworkStatus) -> Unit) {
        // Desktop 简化实现
    }
    
    actual fun stopMonitoring() {
    }
    
    actual fun getCurrentStatus(): NetworkStatus {
        return try {
            val hasInternet = java.net.InetAddress.getByName("google.com").isReachable(3000)
            NetworkStatus(hasInternet, NetworkType.ETHERNET)
        } catch (e: Exception) {
            NetworkStatus(false, NetworkType.NONE)
        }
    }
}

依赖补充

AndroidManifest.xml 配置

xml
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

实战坑点

Android 后台更新限制

API 28+ 限制

Android 9+ 后台应用接收网络状态变化的频率受限。