Quick answer: An ANR, Application Not Responding, fires when your game's main thread fails to respond to input or finish a frame within the system deadline. The trace is a snapshot of every thread at that moment. Find the main thread, read its top stack frames to see what it was doing, and look for blocking calls, lock waits, or heavy work that should never run there.

An Android ANR is not a crash, which is exactly why it confuses people. The game did not die, it froze, and Android decided the player had waited long enough and offered to close it. The artifact you get is a thread dump, a frozen photograph of every thread in the process at the instant the system gave up. Reading it well is a learnable skill: you locate the main thread, follow its stack to whatever it was stuck on, and then ask why that work was happening on the one thread that must always stay responsive. This post teaches that reading.

What an ANR actually is

Android expects your application's main thread, the one that pumps input and drives the UI, to stay responsive. If it cannot dispatch an input event within about five seconds, or finish other key callbacks within their own deadlines, the system raises an ANR. For a game this usually surfaces as a hard freeze: audio loops, the screen stops updating, touches do nothing, and then the system dialog appears. The trigger is always the same underlying condition, which is that the main thread was busy or blocked when it needed to be free.

It is worth internalizing that an ANR is a symptom of starvation, not of an exception. Nothing threw. Some piece of code simply held the main thread too long, whether by computing, waiting on a lock, performing disk or network I/O, or sitting in a call that blocks. Your goal in reading the trace is to identify which of those it was, because the fix is completely different for a slow computation than for a lock you are waiting to acquire.

Finding the main thread in the dump

The ANR trace lists every thread, but only one matters first. The main thread is conventionally named main and is the one bound to the UI and input loop. Scan to it before reading anything else. Its top stack frames tell you what it was executing at the freeze instant, reading from the most recent call downward. If the top frame is inside your own update or render code, you have a slow computation; if it is inside a lock or wait primitive, you have contention.

Pay attention to the thread state recorded next to each thread. A main thread in a runnable state was actively doing work, which points at heavy computation or a tight loop. A main thread that is blocked or waiting is parked on something else, and the trace usually names what it is waiting for. That distinction is the single most useful piece of information in the whole dump, so read the state line deliberately rather than diving straight into frame names.

Identifying what blocked it

Once you know the main thread state, follow the evidence. If it is blocked on a lock, the dump typically tells you which lock object and which other thread currently holds it. Jump to that holder thread and read its stack to see why it is not releasing. This is how you uncover classic deadlocks and contention, where a background thread holds something the main thread needs while doing slow work of its own. The two stacks together tell the whole story.

If the main thread is runnable and deep in your own code, the cause is work that does not belong there: decoding a large asset, parsing a save file, building a level, or running a synchronous query. The fix is to move that work to a background thread and hand back only the finished result. If the top frames are in I/O calls, you have found a file or network operation on the main thread, which should always be off it. Name the category, and the remedy follows directly.

Common game-specific ANR causes

Games hit a recognizable set of ANR patterns. Loading the next level synchronously on the main thread is the classic one: the player taps continue, the game freezes for several seconds reading assets, and Android fires before loading finishes. Save and load operations that touch storage on the main thread are another, especially on slower devices. Shader compilation, large texture uploads, and first-frame initialization can all stall long enough to trip the deadline on low-end hardware that your test phones never represented.

Lock contention around shared game state is subtler. If your audio or networking thread grabs a mutex that your update loop also needs, and that thread occasionally does something slow while holding it, the main thread waits and an ANR can follow. The lesson across all of these is to keep the main thread doing nothing but pumping frames and input, pushing every heavy or blocking operation onto a worker thread and synchronizing with small, fast critical sections.

Setting it up with Bugnet

Bugnet captures ANR-style freezes through its SDK along with the thread dump and the device and OS context, so you are not reconstructing the stall from a one-line player complaint. The report arrives with the main thread stack and the device model attached, which immediately tells you whether the freeze is concentrated on low-end hardware. Because the SDK records game state at capture time, you also see which scene or screen the player was on when the main thread stalled, narrowing the search before you read a single frame.

Occurrence grouping is especially valuable for ANRs because the same blocking call tends to fire across many players. Bugnet folds those into one grouped issue with a count, so a level-load stall that hits hundreds of players shows up as a single high-count item rather than noise. You can filter by device model to confirm a freeze only happens on slow phones, sort by occurrence to prioritize the worst stalls, and tag each issue with the subsystem responsible, all from one dashboard.

Preventing ANRs going forward

The durable fix is architectural: treat the main thread as sacred and budget its time per frame. Audit every place that touches storage, the network, or large allocations, and confirm none of them run on the main thread. Add asynchronous loading screens for level transitions so the heavy work happens off-thread while the UI keeps animating. Test on the cheapest device in your target range, because the deadline that never trips on your flagship phone will trip constantly on a budget handset.

When you do read a real ANR, resist the urge to bump timeouts or paper over the freeze. Each ANR is pointing at a genuine main-thread stall that a real player felt as a frozen game. Fix the root cause, move the work off the main thread, and the same trace pattern stops recurring. A game that keeps its main thread free under load simply feels smooth, and that smoothness is what players quietly reward.

An ANR is a stall, not a crash. Find the main thread, read its state, and move whatever blocked it off that thread.