Repository navigation
Description
Description
Following up on the background-ANR fixes in 5.9.5 (SDK-4794 / PR #2664, prewarm at cold-start entry points) and 5.9.6 (SDK-4822, app-state-aware watchdog). We're still seeing chronic background ANRs on FCMBroadcastReceiver, NotificationDismissReceiver, BootUpReceiver, and SyncJobService on the current SDK, and have version-code-sliced Play Vitals data showing the 5.9.5 prewarm did not measurably reduce them for us.
Setup
onesignal_flutter: was 5.6.3, now 5.6.7 (native Android 5.9.5 → 5.9.8) as of this report- App: Flutter, Android
minSdk24 /targetSdk36 - Device mix skews heavily to budget/entry-level hardware: Tecno, Infinix, Honor, entry-level Redmi and Samsung Galaxy A-series
What we measured
We pulled Google Play Vitals (Android Developer Reporting API), sliced by app version code, comparing builds before and after our 5.6.3 upgrade (which shipped 2026-06-29, pulling in native 5.9.5 and the SDK-4794 prewarm):
| Component (issue) | users, all builds (7d) | users, builds with 5.6.3+ | share unchanged |
|---|---|---|---|
FCMBroadcastReceiver |
16,439 | 16,008 | 97.4% |
NotificationDismissReceiver |
5,963 | 5,907 | 99.1% |
FCMBroadcastReceiver (2nd issue) |
3,024 | 3,005 | 99.4% |
NotificationDismissReceiver (2nd issue) |
2,561 | 2,536 | 99.0% |
BootUpReceiver |
805 | 794 | 98.6% |
SyncJobService |
225 | 216 | 96.0% |
Normalized against install-base drift (OneSignal's share of all Android ANR reports in the same 7-day window, to control for how many devices are on each build): 34.8% pre-5.6.3 → 62.3% post-5.6.3. The upgrade is fully rolled out on our current builds and the cluster is, if anything, a larger share of our total ANR volume than before.
All four components use goAsync() then suspendifyOnIO { ... } (confirmed by reading the 5.9.8 sources). Every ANR dump in this cluster (100+ per capture) shows the main thread idle in nativePollOnce/epoll_pwait with no Runnable thread other than the ART dump-generation thread — which we understand is expected, since goAsync() does not extend the broadcast/service ANR budget; the clock runs from process start through onReceive()/onStartJob() until PendingResult.finish()/job completion, regardless of whether the work moved to a background dispatcher.
Relation to PR #2664
That PR's description explicitly frames prewarming at entry points as the accepted-tradeoff option, over the rejected "durable" option (queue the first dispatch so cold dispatcher construction can never land on a caller thread), specifically because "we have not proven [the race] still fires" in production telemetry, and calls out that "a pathologically slow device could still lose the race."
We believe our fleet is exactly that device population, and the data above is the telemetry that would justify revisiting the durable option — or at minimum, understanding why the prewarm isn't winning the race for us. Happy to share the raw Vitals export (with PII already stripped by the Play Reporting API) if useful for triage.
Ask
- Does the prewarm in SDK-4794 have a known gap for devices this slow (e.g., is there a lower bound on how much lead time it needs before the first
suspendifyOnIOdispatch, and could that lead time not be met on a coldgoAsync()handoff on this hardware class)? - Is there interest in revisiting the "durable" option from PR fix: [SDK-4794] prewarm dispatchers at cold-start entry points #2664 (queueing the first dispatch) given production telemetry that the race is still lost at meaningful volume?
- Is there a way to reduce what runs inside
initWithContext+ the bundle/restore/sync work specifically for cold-background entry points, independent of the dispatcher-construction race?
Happy to provide more detail — version codes, API level breakdown, or raw thread dumps — whatever's useful.
Activity
nikolllay commentedon Sep 25, 2026
Adding a data point from a React Native fleet on
com.onesignal:notifications5.10.2 (react-native-onesignal5.5.14), because it extends this beyond the cold-start case in #2722 / #2751 and shows the dismiss receiver cannot be worked around app-side.Warm, foreground process; both IO threads stuck in TLS handshake
Two full thread dumps (HONOR LGN-NX1 and Samsung SM-E156B, both Android 16) captured at the ANR show the process warm and in the foreground, the main thread doing ordinary frame rendering (
HardwareRenderer.syncAndDrawFrame), the app's JS thread issuing HTTP requests right through the ANR window, and bothOneSignal-IO-1/OneSignal-IO-2parked here:OneSignal-Default-*andOneSignal-SerialIO-1were idle. The dispatchers were warm; prewarm does not help here. System messages, with the HONOR timing suffix:Volume over 30 days for one tenant: 374
FCMBroadcastReceiverand 15NotificationDismissReceiverANR events across ~150 users, Android 12–17, Samsung / HONOR / Xiaomi / Pixel.App-side workaround works for FCM, is impossible for dismiss
For FCM we remove the SDK receiver from the manifest and register our own in the
c2dm.intent.RECEIVEslot; it callsFCMBroadcastReceiver().onReceive(context, intent)on a fresh instance, sogoAsync()returnsnulland thependingResult?.finish()guard from #2667 keeps it safe. The SDK still runs its work on the IO pool; the broadcast just isn't held.That cannot be done for
NotificationDismissReceiver:NotificationDisplayBuilder.getNewBaseDismissIntentbuildsIntent(context, NotificationDismissReceiver::class.java), addressed by class with no intent-filter. Removing the manifest entry makes Android drop the broadcast silently.INotificationServiceExtensioncan't redirect it:NotificationDisplayer.showNotificationapplies the extender beforecreateGenericPendingIntentsForNotifcallssetDeleteIntent, so the SDK overwrites any delete intent an extension sets.So the dismiss case has to be fixed in the SDK. #2712's
BroadcastCompletionwith the 8 s off-main deadline and the ingress lane covers exactly this (it touches both receivers); is there an ETA for it landing?