网络状态监听:ConnectivityManager使用、封装为Flow、在网络变化时自动重试请求
说实话,网络状态监听这个事,很多新手容易忽略。
你想想看,用户在地铁上刷App,信号断断续续的。如果我们的应用不做任何处理,请求失败就直接抛异常,体验能好吗?
我在项目中遇到过好几次这样的场景:用户反馈「为什么我明明有网,你们App却显示加载失败?」。排查下来,其实是网络刚恢复的那一瞬间,请求已经发出去了,但连接还没完全建立起来。
嗯,今天我们就来彻底搞定这个问题。
ConnectivityManager 的基本用法
Android 里监听网络状态,核心就是 ConnectivityManager。这个系统服务负责管理所有的网络连接。
获取它的方式很简单:
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
但要注意,从 Android 7.0 开始,系统废弃了 NetworkInfo,推荐使用 NetworkCapabilities 来判断网络状态。
我个人的习惯是,写一个扩展函数来封装这个判断逻辑:
fun ConnectivityManager.isNetworkAvailable(): Boolean {
val network = activeNetwork ?: return false
val capabilities = getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
这里有个坑,我曾经踩过:activeNetwork 可能为 null,即使 Wi-Fi 或移动数据是打开的。为什么?因为系统需要一点时间来建立连接。所以一定要做空安全判断。
注册网络状态回调
监听网络变化,传统做法是注册 BroadcastReceiver。但 Android 官方更推荐使用 NetworkCallback,因为它更高效、更精准。
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
// 网络可用
}
override fun onLost(network: Network) {
// 网络断开
}
override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) {
// 网络能力变化,比如从Wi-Fi切到移动数据
}
}
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
callback
)
记得在不需要的时候取消注册:
connectivityManager.unregisterNetworkCallback(callback)
封装为 Flow:让网络状态变成响应式数据流
直接使用回调,在 MVVM 架构里有点别扭。ViewModel 需要持有 callback 引用,还要处理生命周期。
更好的方式,是把网络状态封装成一个 Flow。这样任何地方都可以 collect,而且天然支持协程取消。
来看看我的封装思路:
class NetworkMonitor(private val context: Context) {
val networkState: Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available)
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable)
}
override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) {
val hasInternet = capabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_INTERNET
)
trySend(if (hasInternet) NetworkStatus.Available else NetworkStatus.Unavailable)
}
}
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
callback
)
// 发送初始状态
val initialStatus = if (connectivityManager.isNetworkAvailable()) {
NetworkStatus.Available
} else {
NetworkStatus.Unavailable
}
trySend(initialStatus)
// 协程取消时自动取消注册
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
}
sealed class NetworkStatus {
object Available : NetworkStatus()
object Unavailable : NetworkStatus()
}
这里用了 callbackFlow,它是专门用来把回调式 API 转为 Flow 的。注意 trySend 而不是 send,因为回调可能发生在非协程上下文中。
NetworkStatus 里加一个枚举字段。我在做视频类App时,就根据网络类型决定是否自动播放。
网络变化时自动重试请求
好了,现在网络状态已经变成 Flow 了。怎么用它来自动重试呢?
核心思路是:当请求失败时,监听网络状态 Flow,等到网络恢复后自动重试。
我封装了一个 retryOnNetworkChange 操作符:
fun <T> Flow<T>.retryOnNetworkChange(
networkMonitor: NetworkMonitor,
maxRetries: Int = Int.MAX_VALUE
): Flow<T> = this.retryWhen { cause, attempt ->
if (cause is IOException && attempt < maxRetries) {
// 等待网络恢复
networkMonitor.networkState
.filter { it == NetworkStatus.Available }
.first()
true // 继续重试
} else {
false // 不重试
}
}
使用起来非常简洁:
viewModelScope.launch {
apiService.fetchData()
.retryOnNetworkChange(networkMonitor, maxRetries = 3)
.catch { e ->
_uiState.value = UiState.Error("加载失败,请检查网络")
}
.collect { data ->
_uiState.value = UiState.Success(data)
}
}
这里有个细节:filter { it == NetworkStatus.Available }.first() 会挂起当前协程,直到网络恢复。不会占用任何 CPU 资源,非常优雅。
maxRetries,并且加上指数退避。
完整的架构图
下面这张图展示了整个数据流的走向:
实际项目中的最佳实践
我在多个项目里用过这套方案,总结出几个要点:
- 不要在主线程 collect:网络状态 Flow 虽然不耗时,但 collect 操作本身会挂起协程。记得用
viewModelScope或lifecycleScope。 - 考虑冷启动场景:App 启动时,ConnectivityManager 可能还没准备好。我习惯在 Application 的
onCreate里初始化 NetworkMonitor,并延迟 500ms 再发送初始状态。 - 区分网络类型:如果用户用的是移动数据,下载大文件前应该弹个提示。可以在
NetworkStatus里加上isMetered字段。 - 测试要覆盖:用模拟器切换网络模式,或者用
NetworkCallback的 mock 来测试重试逻辑。我曾经因为没测试「飞行模式切换」场景,上线后出了 bug。
核心要点:
- 使用 ConnectivityManager.NetworkCallback 替代 BroadcastReceiver
- 用 callbackFlow 将回调转为 Flow,自动管理生命周期
- retryWhen + filter + first 实现优雅的网络恢复重试
- 设置重试上限,避免无限重试导致服务器压力
好了,这套方案我用了两年多,线上表现很稳定。你可以在自己的项目里试试看,有问题随时调整。