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

Jetpack Compose中少量回调注册却触发TooManyRequestsException崩溃

Jetpack Compose网络监听触发TooManyRequestsException崩溃问题

问题背景

我参考某教程在全Jetpack Compose应用中实现了网络连接监听功能,联网状态下运行正常,但按特定步骤操作后应用崩溃,抛出android.net.ConnectivityManager$TooManyRequestsException异常——实际注册的回调数量极少,却触发了该错误。

实现代码

sealed class ConnectionState {
    data object Available : ConnectionState()
    data object Unavailable : ConnectionState()
}

val Context.isConnected: ConnectionState
    get() {
        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val network = connectivityManager.activeNetwork ?: return ConnectionState.Unavailable
        val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return ConnectionState.Unavailable

        return when {
            activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> ConnectionState.Available
            activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> ConnectionState.Available
            else -> ConnectionState.Unavailable
        }
    }

/**
 * observe availability or unavailability of Internet connection
 */
fun Context.observeConnectivityAsFlow() = callbackFlow {
    Log.d("Bazinga", "callbackFlow")
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    val callback = NetworkCallback { connectionState -> trySend(connectionState) }

    val networkRequest = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()

    val registered = mutableListOf<NetworkCallback>()
    Log.d("Bazinga", "register - $callback")
    registered.add(callback)
    Log.d("Bazinga", "registered: $registered")
    connectivityManager.registerNetworkCallback(networkRequest, callback)

    trySend(isConnected)

    awaitClose {
        Log.d("Bazinga", "unregister - $callback")
        registered.remove(callback)
        connectivityManager.unregisterNetworkCallback(callback)
    }
}

fun NetworkCallback(callback: (ConnectionState) -> Unit): ConnectivityManager.NetworkCallback {
    return object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            callback(ConnectionState.Available)
        }

        override fun onLost(network: Network) {
            callback(ConnectionState.Unavailable)
        }
    }
}

@ExperimentalCoroutinesApi
@Composable
fun connectivityState(): State<ConnectionState> {
    val context = LocalContext.current
    return produceState(initialValue = context.isConnected) {
        Log.d("Bazinga", "produceState")
        context.observeConnectivityAsFlow().collect { value = it }
    }
}

@Composable
fun ObserveConnection(onConnectionResumed: () -> Unit) {
    val connection by connectivityState()
    LaunchedEffect(key1 = Unit) {
        snapshotFlow { connection }
            .collect {
                if (it == ConnectionState.Available) onConnectionResumed()
            }
    }
}

使用方式

在需要监听网络恢复事件的页面,调用:

ObserveConnection(viewModel::onConnectionResumed)

崩溃复现步骤

  • 开启网络
  • 进入包含ObserveConnection调用的Screen A
  • 进入无额外监听逻辑的Screen B
  • 关闭网络
  • 按系统返回键回到Screen A
  • 再次进入Screen B
  • 按系统返回键
  • 应用崩溃

注册/注销日志

produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28]
unregister - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28
produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d]
unregister - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d
produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@3636fc
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@3636fc]

崩溃堆栈信息

FATAL EXCEPTION: main
                 Process: com.mypackagename, PID: 12545
                 android.net.ConnectivityManager$TooManyRequestsException
                    at android.net.ConnectivityManager.convertServiceException(ConnectivityManager.java:4032)
                    at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:4221)
                    at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:4228)
                    at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:4610)
                    at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:4580)
                    at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invokeSuspend(NetworkUtils.kt:54)
                    at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invoke(Unknown Source:8)
                    at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invoke(Unknown Source:4)
                    at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo$suspendImpl(Builders.kt:320)
                    at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo(Unknown Source:0)
                    at kotlinx.coroutines.flow.CallbackFlowBuilder.collectTo(Builders.kt:334)
                    at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
                    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
                    at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
                    at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
                    at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
                    at android.os.Handler.handleCallback(Handler.java:942)
                    at android.os.Handler.dispatchMessage(Handler.java:99)
                    at android.os.Looper.loopOnce(Looper.java:201)
                    at android.os.Looper.loop(Looper.java:288)
                    at android.app.ActivityThread.main(ActivityThread.java:7872)
                    at java.lang.reflect.Method.invoke(Native Method)
                    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
                    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@8fdf886, androidx.compose.runtime.BroadcastFrameClock@37fd547, StandaloneCoroutine{Cancelling}@c53a174, AndroidUiDispatcher@e85469d]

问题分析与修复方案

核心问题

日志显示回调已注销,但实际可能存在注销操作未执行的情况(比如协程异常导致awaitClose块未触发);同时每次进入页面都会创建新的Flow和回调实例,频繁注册/注销会触发系统的请求限制,即使表面数量不多。

修复方案

1. 全局单例化网络监听Flow

避免重复创建监听实例,将Flow设计为全局单例:

// 全局单例Flow,仅初始化一次
val Context.connectivityFlow: Flow<ConnectionState> by lazy {
    callbackFlow {
        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                trySend(ConnectionState.Available)
            }

            override fun onLost(network: Network) {
                trySend(ConnectionState.Unavailable)
            }
        }

        val networkRequest = NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()

        connectivityManager.registerNetworkCallback(networkRequest, callback)
        // 发送初始状态
        trySend(isConnected)

        awaitClose {
            // 确保注销逻辑一定会执行
            connectivityManager.unregisterNetworkCallback(callback)
        }
    }.distinctUntilChanged() // 过滤重复状态,减少不必要回调
}

2. 修改Composable状态获取逻辑

使用全局单例Flow,避免重复创建监听:

@Composable
fun connectivityState(): State<ConnectionState> {
    val context = LocalContext.current
    return produceState(initialValue = context.isConnected) {
        context.connectivityFlow.collect { value = it }
    }
}

3. 优化ObserveConnection逻辑

去掉冗余的snapshotFlow,直接监听状态变化:

@Composable
fun ObserveConnection(onConnectionResumed: () -> Unit) {
    val connection by connectivityState()
    // 仅在连接状态变为Available时触发回调
    LaunchedEffect(connection) {
        if (connection == ConnectionState.Available) {
            onConnectionResumed()
        }
    }
}

额外注意事项

  • 确保awaitClose块中的注销逻辑一定会执行,避免因协程异常导致回调泄漏
  • 使用distinctUntilChanged()过滤重复状态,减少不必要的UI触发
  • Android 12及以上版本对ConnectivityManager的注册限制更严格,单例模式能有效规避请求超限问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 00:20:59