From 7900f09f458fe1101be9a983264689a9d011c5cf Mon Sep 17 00:00:00 2001 From: Or Leib Date: Sat, 3 Oct 2026 20:40:10 +0300 Subject: [PATCH] fix(network): avoid ANR by moving ConnectivityManager calls off the main thread on Android handleOnResume and handleOnPause ran registerDefaultNetworkCallback, unregisterNetworkCallback and getNetworkStatus (getActiveNetwork and getNetworkCapabilities) on the main thread. These are synchronous binder calls into system_server and can block long enough to trigger an ANR. Run that work on the bridge plugin thread via Bridge.execute, the same serial thread plugin methods run on, so pause/resume stay ordered with each other and with getStatus. Also reuse the active network in getNetworkStatus instead of querying it twice. Closes #2563 --- .../capacitorjs/plugins/network/Network.java | 2 +- .../plugins/network/NetworkPlugin.java | 44 +++++++++++-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java b/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java index 8f8842feec..da88128eed 100644 --- a/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java +++ b/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java @@ -75,7 +75,7 @@ public NetworkStatus getNetworkStatus() { NetworkStatus networkStatus = new NetworkStatus(); if (this.connectivityManager != null) { android.net.Network activeNetwork = this.connectivityManager.getActiveNetwork(); - NetworkCapabilities capabilities = this.connectivityManager.getNetworkCapabilities(this.connectivityManager.getActiveNetwork()); + NetworkCapabilities capabilities = this.connectivityManager.getNetworkCapabilities(activeNetwork); if (activeNetwork != null && capabilities != null) { networkStatus.connected = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && diff --git a/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java b/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java index d1fe63ef8d..194f1df30a 100644 --- a/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java +++ b/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java @@ -52,33 +52,41 @@ public void getStatus(PluginCall call) { } /** - * Register the IntentReceiver on resume + * Register the network callback on resume. + * The ConnectivityManager calls are binder IPCs that can block for a long time, so they run + * on the plugin thread instead of the main thread to avoid ANRs. The plugin thread is a single + * serial thread, so this work stays ordered with handleOnPause and with plugin method calls. */ @Override protected void handleOnResume() { - implementation.startMonitoring(); - NetworkStatus afterPauseNetworkStatus = implementation.getNetworkStatus(); - if ( - prePauseNetworkStatus != null && - !afterPauseNetworkStatus.connected && - (prePauseNetworkStatus.connected || afterPauseNetworkStatus.connectionType != prePauseNetworkStatus.connectionType) - ) { - Log.d( - "Capacitor/NetworkPlugin", - "Detected pre-pause and after-pause network status mismatch. Updating network status and notifying listeners." - ); - this.updateNetworkStatus(); - } - this.prePauseNetworkStatus = null; + getBridge().execute(() -> { + implementation.startMonitoring(); + NetworkStatus afterPauseNetworkStatus = implementation.getNetworkStatus(); + if ( + prePauseNetworkStatus != null && + !afterPauseNetworkStatus.connected && + (prePauseNetworkStatus.connected || afterPauseNetworkStatus.connectionType != prePauseNetworkStatus.connectionType) + ) { + Log.d( + "Capacitor/NetworkPlugin", + "Detected pre-pause and after-pause network status mismatch. Updating network status and notifying listeners." + ); + this.updateNetworkStatus(); + } + this.prePauseNetworkStatus = null; + }); } /** - * Unregister the IntentReceiver on pause to avoid leaking it + * Unregister the network callback on pause to avoid leaking it. + * Runs on the plugin thread for the same reason as handleOnResume. */ @Override protected void handleOnPause() { - this.prePauseNetworkStatus = implementation.getNetworkStatus(); - implementation.stopMonitoring(); + getBridge().execute(() -> { + this.prePauseNetworkStatus = implementation.getNetworkStatus(); + implementation.stopMonitoring(); + }); } private void updateNetworkStatus() {