Engineering analysis, impact classification, root cause, and fix added below the original report. — AR
Engineering analysis
Under the sdk_background_threading feature flag, the public service accessors (OneSignal.getUser(), getInAppMessages(), getNotifications(), getSession(), getLocation()) block the calling thread on runBlocking(OneSignalDispatchers.IO) on every call — even after init has already reached SUCCESS. On Flutter, the plugin resolves these managers synchronously on the platform/main thread inside lifecycleInit, so during cold start (when the IO pool is contended) the main thread parks waiting for a free IO worker, producing ANRs. This matches both reported stack traces.
Impact
Not a crash. The main thread is parked inside runBlocking and the stack bottoms out at Looper.loop / ActivityThread.main (the platform message loop), so the OS surfaces this as an ANR (Android vitals anomaly), not an uncaught-exception force-close. Functional consequence: UI freeze of up to several seconds at startup on low-end devices; self-recovering once init completes and an IO worker frees up.
Severity: High (priority 2) — main-thread block on the hottest startup path with broad user-visible impact (20k+ users, vitals threshold breached), but recoverable and not a fatal crash.
Root cause
getServiceWithFeatureGate has no SUCCESS fast-path in the FF-on branch. The legacy (FF-off) branch returns the service directly when initState == SUCCESS, but the FF-on branch unconditionally routes through waitAndReturn -> waitForInit -> runBlocking(runtimeIoDispatcher) where runtimeIoDispatcher == OneSignalDispatchers.IO. Every accessor therefore pays a dispatcher round-trip and parks the caller, even when init has long since finished.
Why this dominates other ANR sites
- Accessors are the hottest path (touched on essentially every launch / interaction), vs. occasional
login/logout.
- FF-on removed the lock-free
SUCCESS return, so the park happens on every call, not just during in-progress init.
- Cold-start IO contention: real init work (bootstrap, prefs/disk, network) queues on the same
OneSignalDispatchers.IO pool, so the trivial accessor waits behind it.
- The Flutter wrapper calls these getters synchronously on the platform/main thread in
lifecycleInit, early and repeatedly — the native paths avoid this by going off-main.
Fix (Android SDK)
Add a lock-free SUCCESS fast-path to the FF-on branch of getServiceWithFeatureGate: SUCCESS is terminal and never flips back, so the service is constructable and can be returned synchronously without runBlocking. IN_PROGRESS / FAILED / NOT_STARTED still go through waitAndReturn (preserving block-or-throw semantics).
Regression test added in SDKInitTests that saturates OneSignalDispatchers.IO and asserts a SUCCESS-state FF-on accessor still returns synchronously (verified to fail without the fix).
PR: OneSignal/OneSignal-Android-SDK#2665
Follow-ups (separate, lower priority)
- The consent getters (
consentRequired/consentGiven/disableGMSMissingPrompt) have the same pattern via blockingGet (always runBlocking even when initialized). Not in the reported traces.
- Durable Flutter-side fix: resolve managers off the platform thread in
lifecycleInit (OneSignal-Flutter-SDK).
Original report
What happened?
After updating onesignal_flutter from 5.5.6 to 5.6.0 to solve ANR issues, this latest version is causing more of them. I have more than 20k users affected and an alert from play console because the threshold was reached
"New Android vitals anomalies were detected"
Now I don't want to update to 5.6.2 because how do I know if this will get worse?
Steps to reproduce?
1. Install onesignal_flutter 5.6.0
2. Test in a low end device like TECNO TECNO-KL4, TECNO TECNO-BG6, Infinix Infinix-X6525, TECNO TECNO-KI5k with Android 14 (SDK 34), Android 13 (SDK 33), Android 15 (SDK 35), Android 12 (SDK 31), Android 16 (SDK 36)
What did you expect to happen?
SDK should not block main thread
OneSignal Flutter SDK version
5.6.0
Which platform(s) are affected?
Relevant log output
Thread 1
"main" tid=1 Timed Waiting
Main thread
at jdk.internal.misc.Unsafe.park (Native method)
at java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:276)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking (BlockingCoroutine.java:97)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking (BuildersKt__Builders.kt:70)
at kotlinx.coroutines.BuildersKt.runBlocking (Builders.kt:1)
at com.onesignal.internal.OneSignalImp.waitForInit (OneSignalImp.kt:605)
at com.onesignal.internal.OneSignalImp.waitForInit$default (OneSignalImp.kt:604)
at com.onesignal.internal.OneSignalImp.waitAndReturn (OneSignalImp.kt:711)
at com.onesignal.internal.OneSignalImp.getServiceWithFeatureGate (OneSignalImp.kt:717)
at com.onesignal.internal.OneSignalImp.getInAppMessages (OneSignalImp.kt:138)
at com.onesignal.OneSignal.getInAppMessages (OneSignal.kt:78)
at com.onesignal.flutter.OneSignalInAppMessages.lifecycleInit (OneSignalInAppMessages.java:100)
... Looper.loop ... ActivityThread.main
This one is less frequent but is there:
at com.onesignal.internal.OneSignalImp.getUser (OneSignalImp.kt:142)
at com.onesignal.OneSignal.getUser (OneSignal.kt:46)
at com.onesignal.flutter.OneSignalUser.lifecycleInit (OneSignalUser.java:64)
... Looper.loop ... ActivityThread.main
Code of Conduct
Engineering analysis
Under the
sdk_background_threadingfeature flag, the public service accessors (OneSignal.getUser(),getInAppMessages(),getNotifications(),getSession(),getLocation()) block the calling thread onrunBlocking(OneSignalDispatchers.IO)on every call — even after init has already reachedSUCCESS. On Flutter, the plugin resolves these managers synchronously on the platform/main thread insidelifecycleInit, so during cold start (when the IO pool is contended) the main thread parks waiting for a free IO worker, producing ANRs. This matches both reported stack traces.Impact
Not a crash. The main thread is parked inside
runBlockingand the stack bottoms out atLooper.loop/ActivityThread.main(the platform message loop), so the OS surfaces this as an ANR (Android vitals anomaly), not an uncaught-exception force-close. Functional consequence: UI freeze of up to several seconds at startup on low-end devices; self-recovering once init completes and an IO worker frees up.Severity: High (priority 2) — main-thread block on the hottest startup path with broad user-visible impact (20k+ users, vitals threshold breached), but recoverable and not a fatal crash.
Root cause
getServiceWithFeatureGatehas noSUCCESSfast-path in the FF-on branch. The legacy (FF-off) branch returns the service directly wheninitState == SUCCESS, but the FF-on branch unconditionally routes throughwaitAndReturn -> waitForInit -> runBlocking(runtimeIoDispatcher)whereruntimeIoDispatcher == OneSignalDispatchers.IO. Every accessor therefore pays a dispatcher round-trip and parks the caller, even when init has long since finished.Why this dominates other ANR sites
login/logout.SUCCESSreturn, so the park happens on every call, not just during in-progress init.OneSignalDispatchers.IOpool, so the trivial accessor waits behind it.lifecycleInit, early and repeatedly — the native paths avoid this by going off-main.Fix (Android SDK)
Add a lock-free
SUCCESSfast-path to the FF-on branch ofgetServiceWithFeatureGate:SUCCESSis terminal and never flips back, so the service is constructable and can be returned synchronously withoutrunBlocking.IN_PROGRESS/FAILED/NOT_STARTEDstill go throughwaitAndReturn(preserving block-or-throw semantics).Regression test added in
SDKInitTeststhat saturatesOneSignalDispatchers.IOand asserts aSUCCESS-state FF-on accessor still returns synchronously (verified to fail without the fix).PR: OneSignal/OneSignal-Android-SDK#2665
Follow-ups (separate, lower priority)
consentRequired/consentGiven/disableGMSMissingPrompt) have the same pattern viablockingGet(alwaysrunBlockingeven when initialized). Not in the reported traces.lifecycleInit(OneSignal-Flutter-SDK).Original report
What happened?
After updating
onesignal_flutterfrom 5.5.6 to 5.6.0 to solve ANR issues, this latest version is causing more of them. I have more than 20k users affected and an alert from play console because the threshold was reachedNow I don't want to update to 5.6.2 because how do I know if this will get worse?
Steps to reproduce?
What did you expect to happen?
SDK should not block main thread
OneSignal Flutter SDK version
5.6.0
Which platform(s) are affected?
Relevant log output
Code of Conduct