Skip to content

Fix background ANR from connectivity broadcast receiver - #23140

Merged
nbradbury merged 13 commits into
trunkfrom
issue/network-changed-anr
Jul 24, 2026
Merged

Fix background ANR from connectivity broadcast receiver#23140
nbradbury merged 13 commits into
trunkfrom
issue/network-changed-anr

Conversation

@nbradbury

@nbradbury nbradbury commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Potentially fixes CMM-2174.

Replaces the deprecated BroadcastReceiver (ConnectionChangeReceiver) with a ConnectivityManager.NetworkCallback registered on a background thread. This PR also replaces the legacy EventBus connectivity handling with a more modern LiveData approach.

Why

The Sentry issue JETPACK-ANDROID-PB9 is a high-volume "Background ANR". The old receiver's onReceive ran 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 queues onReceive on 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.

Note: this removes one well-understood main-thread vector but it's not a guaranteed solution; judge success by the Sentry rate over the next releases.

Testing

  • Run the app
  • Enable airplane mode
  • Verify the "No connection" bar appears
  • Disable airplane mode
  • Verify the "No connection" bar disappears

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
@dangermattic

dangermattic commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator
2 Warnings
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr23140-23724be
Build Number1498
Application IDcom.jetpack.android.prealpha
Commit23724be
Installation URL35bal791k1adg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr23140-23724be
Build Number1498
Application IDorg.wordpress.android.prealpha
Commit23724be
Installation URL6nc6trh6bb40o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.72414% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.84%. Comparing base (9e0dba0) to head (23724be).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
...ess/android/networking/NetworkConnectionMonitor.kt 53.84% 12 Missing ⚠️
.../main/java/org/wordpress/android/AppInitializer.kt 0.00% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- 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.
@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This 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.
@nbradbury
nbradbury marked this pull request as ready for review July 23, 2026 18:58
@nbradbury
nbradbury requested a review from adalpari July 23, 2026 18:58
@nbradbury
nbradbury marked this pull request as draft July 23, 2026 19:11
@nbradbury
nbradbury removed the request for review from adalpari July 23, 2026 19:12
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.
@nbradbury
nbradbury marked this pull request as ready for review July 24, 2026 12:18
@nbradbury
nbradbury requested a review from adalpari July 24, 2026 12:18

@adalpari adalpari left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and works as expected!

@nbradbury
nbradbury enabled auto-merge (squash) July 24, 2026 15:51
@nbradbury
nbradbury merged commit 3ab17e4 into trunk Jul 24, 2026
17 of 22 checks passed
@nbradbury
nbradbury deleted the issue/network-changed-anr branch July 24, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants