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
相关产品推荐
相关产品推荐

