Fix background ANR from connectivity broadcast receiver - #23140
Conversation
Replace the deprecated CONNECTIVITY_ACTION BroadcastReceiver (ConnectionChangeReceiver) with a ConnectivityManager.NetworkCallback (NetworkConnectionMonitor) registered on a background thread. Connectivity checks and the EventBus dispatch no longer run on the main thread, and a NetworkCallback is not a broadcast so it cannot trip the background-broadcast ANR timeout. The monitor is registered once for the process lifetime, dropping the unreliable foreground/background register/unregister workaround. The ConnectionChangeEvent contract is preserved, so subscribers are unchanged apart from the renamed enclosing type. Sentry: JETPACK-ANDROID-PB9
Generated by 🚫 Danger |
|
|
|
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trunk #23140 +/- ##
=======================================
Coverage 37.83% 37.84%
=======================================
Files 2345 2345
Lines 127515 127511 -4
Branches 17711 17709 -2
=======================================
+ Hits 48244 48255 +11
+ Misses 75313 75298 -15
Partials 3958 3958 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- SuggestionActivity's ConnectionChangeEvent subscriber used a bare @subscribe (POSTING). Since the event is now posted from a background thread, its updateEmptyView() touched Views off the main thread. Pin it to ThreadMode.MAIN. - NetworkConnectionMonitor.onLost no longer assumes the device is offline: during a network handover (e.g. Wi-Fi -> cellular) the replacement is already the default, so re-query the active network's capabilities instead of posting a spurious disconnected event.
🤖 Build Failure AnalysisThis build has failures. Claude has analyzed them - check the build annotations for details. |
The monitor is registered once for the whole process lifetime and never torn down, so stop() had no callers. Removing it also lets the handlerThread and networkCallback fields go — they were only read by stop(). connectivityManager stays since hasActiveConnection() uses it.
Collapse hasActiveConnection's guard clauses into a safe-call chain so it has two returns instead of four. No behavior change.
Fix 2 re-queried ConnectivityManager.getActiveNetwork() in onLost to avoid misreporting a network handover as a disconnect. On real devices getActiveNetwork() can still return the network being torn down at that instant, so a genuine disconnect was suppressed by the de-dupe, leaving wasConnected stuck true. The "No connection" bar then only updated on the next onResume poll, not live. Track the set of available internet-capable networks instead (registerNetworkCallback with a NET_CAPABILITY_INTERNET request): a handover adds the replacement before removing the old network so the set never empties (no false disconnect), while a real disconnect empties it reliably. Removes the fragile getActiveNetwork() query.
Clarify that the available-networks set only stays non-empty during a handover when the replacement network is already up; no code change.
Drop the ConnectionChangeEvent-over-EventBus delivery introduced with NetworkConnectionMonitor and expose the connected state as a LiveData<Boolean> that subscribers observe via their lifecycle owner. Removes EventBus entirely from SuggestionActivity and BlogPreferencesActivity; the other subscribers keep EventBus for unrelated events.
Extract the NetworkCallback logic into @VisibleForTesting onNetworkAvailable/ onNetworkLost so the connected-state model can be tested without the Android framework wiring in start(). Add NetworkConnectionMonitorTest covering: connected on first network, disconnected when the last network is lost, no false disconnect during a handover, de-dupe when unchanged, and re-emit on reconnect. Satisfies the Danger check requiring tests for the new class.
LiveData replays the current value to each new observer, so a side effect that should only run on an offline->online transition also fired on open. - SuggestionActivity: only call viewModel.onConnectionChanged (which refreshes suggestions) on a genuine offline->online transition. The initial replay was triggering a redundant fetch on top of the one started in viewModel.init(). - ReaderPostListFragment: initialise lastConnected to true so the first replayed "connected" value doesn't re-run the search (e.g. on a restored fragment with an active search); only a real transition resubmits.
adalpari
left a comment
There was a problem hiding this comment.
LGTM and works as expected!


What
Potentially fixes CMM-2174.
Replaces the deprecated
BroadcastReceiver(ConnectionChangeReceiver) with aConnectivityManager.NetworkCallbackregistered on a background thread. This PR also replaces the legacyEventBusconnectivity handling with a more modernLiveDataapproach.Why
The Sentry issue JETPACK-ANDROID-PB9 is a high-volume "Background ANR". The old receiver's
onReceiveran on the main thread and was registered via an admittedly unreliable foreground/background workaround, so it could stay registered while backgrounded. A connectivity broadcast arriving then queuesonReceiveon the main thread; if main is momentarily busy, the receiver dispatch exceeds the background-broadcast timeout → Background ANR.A NetworkCallback is not a broadcast (so it isn't subject to background-broadcast ANR timeouts) and runs on a background
Handler, keeping all connectivity work off the main thread. It's registered once for the process lifetime, dropping the flaky register/unregister lifecycle.Testing