Skip to content

[Bug]: 5.6.0 causing multiple ANR issues on Android #1163

Description

@martinpelli

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

  1. Accessors are the hottest path (touched on essentially every launch / interaction), vs. occasional login/logout.
  2. FF-on removed the lock-free SUCCESS return, so the park happens on every call, not just during in-progress init.
  3. 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.
  4. 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?

  • iOS
  • Android

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

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions