Google Play Pre-Launch Report ANR in Unity IL2CPP Build - Startup Thread and Splash Flow Fix

Problem: Google Play Console shows an ANR (Application Not Responding) in the pre-launch report, Android Vitals, or crash reports, often with stack traces pointing at UnityPlayer, native, or your first activity during cold start. The dialog users never see on a lab device still counts as a blocked main thread long enough that the system would have shown “app isn’t responding.”

Quick direction: On Unity IL2CPP Android builds, most startup ANRs mean the main thread is doing too much work before the first frame—or waiting on I/O, network, synchronous asset loads, or plugins that initialize on the UI thread. Your job is to shorten and split that work, then prove it with a capture or a clean pre-launch run.

Why this happens

  1. Heavy Awake / Start / OnEnable chains – Large Resources.Load, Addressables synchronous waits (for example WaitForCompletion() on an async handle), big Instantiate batches, or LINQ over huge lists on the first scene.
  2. Splash screen held too longUnity splash plus custom static splash plus blocking init before SceneManager loads gameplay.
  3. Synchronous third-party SDKs – Ads, analytics, attribution, or social SDKs that block during Application startup hooks.
  4. Shader or pipeline warmup – First-frame compilation or graphics setup spikes on low pre-launch devices.
  5. IL2CPP and linking – Less often the direct cause of ANR, but larger binaries and more static constructors can stretch startup if combined with the above.
  6. Deadlock or lock contention – Rare but real: main thread waits on background work that waits on main.

Google’s pre-launch devices are not your dev phone; they are often mid-range with stricter watchdog timing—so “works on my device” is weak evidence.

Fix 1 - Audit first scene and strip blocking calls

  1. Identify everything that runs before the first interactive frame: boot scene, splash scene, RuntimeInitializeOnLoadMethod, and static constructors in types touched early.
  2. Search for .WaitForCompletion(), LoadAsset (synchronous), File.ReadAllText, WebRequest without async, Thread.Sleep, lock around long work on the main thread.
  3. Replace with async Addressables loads, async/await, UniTask (if you use it), or deferred init on frame 2+ via StartCoroutine / next frame callbacks.
  4. Move non-critical SDK Init to after first frame or after menu shown—only if their docs allow late init (ads often need early registration; split consent vs heavy work per vendor guidance).

Verification: Run Development Build with Autoconnect Profiler, capture first 5 seconds, and confirm CPU main thread is not pegged continuously without yielding.

Fix 2 - Shorten and decouple the splash flow

  1. In Player Settings → Splash Image, avoid unnecessarily long fixed splash if you use Unity splash plus your art—combine branding into one phase where possible.
  2. If you use Unity’s Splash Screen API, ensure you are not performing blocking work while splash is visible without async progress.
  3. Load a minimal bootstrap scene first (camera + progress UI only), then asynchronously load the heavy scene.

Verification: Time from tap icon to first Update of gameplay UI on a mid-tier device; aim for no multi-second main-thread stall in profiler.

Fix 3 - Plugins and native callbacks

  1. List all Android plugins under Assets/Plugins/Android and Packages that run on launch.
  2. For each, read the vendor’s Unity integration note: move initialization off the critical path where supported (e.g. background thread safe APIs only).
  3. Disable non-essential plugins in a test build and re-run pre-launch to bisect which library correlates with ANR stacks.

Verification: Compare systrace / perfetto or Android Studio CPU capture with and without a suspect plugin.

Fix 4 - Graphics and first-frame cost

  1. Pre-warm critical shaders in a controlled moment (loading screen with progress), not hidden inside blocking calls on frame 0.
  2. Reduce default quality tier on low devices so first scene does not allocate huge shadow / post stacks immediately.
  3. Avoid sync Shader.Find in Awake on startup paths.

Verification: Frame Debugger / Profiler on release-like build with development options enabled for profiling.

Fix 5 - Capture and read the ANR properly

  1. In Play Console, open Android Vitals or Pre-launch report and download ANR or traces if offered; note “Input dispatching timed out” and main thread stack.
  2. If stacks show UnityMain stuck in your C# (symbols), enable symbol upload / line numbers for IL2CPP builds so managed frames are readable (Unity Cloud Diagnostics or Play deobfuscation / symbols as applicable).
  3. Reproduce locally with adb shell am start -W and strict mode / ANR settings where useful; some teams use Android Studio Profiler attached on cold start.

Verification: After fixes, upload a new .aab to internal testing and wait for a fresh pre-launch cycle; compare ANR rate before and after.

Alternative fixes

  • Mono backend (if still available for your Unity version) – Useful only as a diagnostic A/B; shipping backend should match store policy and Unity support. Do not switch blindly.
  • Strip managed code carefully – Over-aggressive stripping can cause lazy exceptions or unexpected paths; fix root blocking first.
  • Multidex / large dex issues – Rare for typical Unity games but if you merged many Java libs, startup class loading can hurt; profile Java side if stacks point there.

Prevention tips

  • Maintain a “startup budget” document: max milliseconds of main-thread work per phase (splash, menu, game).
  • Add a development-only overlay that logs frame time during first N seconds.
  • Run pre-launch or Firebase Test Lab on every release candidate, not only after rejection.

FAQ

Does every pre-launch ANR mean users see a dialog?
No. The system detects unresponsiveness; lab robots still record it as quality risk and it can hurt ranking.

Are IL2CPP and ANR the same problem?
IL2CPP changes build layout; ANR is main-thread responsiveness. Fix threading and init order first.

Can Addressables cause ANR?
Yes, if you use synchronous wait APIs or massive synchronous catalog parse on first frame.

Related links

Bookmark this fix if you ship Android often, and share it with anyone who owns SDK initialization or first-scene loading.