appranker
  • Google Play
  • Android
  • Ranking

ANR rate on Google Play: how to diagnose and fix ANRs

ANR rate on Google Play explained: the 0.47% and 8% thresholds, how to read ANR clusters in Play Console, code fixes that work and a script to track it daily.

AppRanker team15 min read

A hand holding an Android phone that shows the Android 11 logo, above a patterned tile floor

Your ANR rate on Google Play is one of the few ranking signals Google spells out in writing. When the main thread of your Android app stops responding, Android records an "Application Not Responding" error, and Play counts the share of your daily users who hit one. Cross the line and, in Google's words, your app "is likely to be less discoverable." No keyword, screenshot or review reply will make up for that.

This guide covers what the metric measures, the exact thresholds, how to find the clusters that matter in Play Console, the code changes that fix the common causes, how to catch ANRs before your users do, and a small script that pulls your ANR rate per device every day. The facts come from Google's Android vitals overview, the ANR metric page and the guide to diagnosing and fixing ANRs, all updated in October 2026.

Key takeaways

  • The user-perceived ANR rate is a core vital. The bad behavior thresholds are 0.47% of daily active users across all devices and 8% on any single phone model.
  • Only ANRs of type Input dispatching timed out count as user-perceived today, so filter Play Console to foreground issues first.
  • Play judges you on a 28-day average. A fix shipped today takes weeks to fully show up, so act on the first warning.
  • Most ANRs come from the same few causes: I/O or heavy work on the main thread, lock contention, slow broadcast receivers and slow startup.
  • Clusters that show nativePollOnce for an input dispatch ANR are usually not actionable. Fix the clusters with real frames first.

What the ANR rate on Google Play measures

An ANR happens when the UI thread is blocked for too long. For input, "too long" is 5 seconds: if your app has not responded to a touch or key press in that time, Android raises the ANR, and if the app is in the foreground it shows a dialog that lets the user wait or close the app.

Android vitals reports three related numbers, defined on the ANR metric page:

Metric Google's definition
ANR rate "The percentage of your daily active users who experienced any type of ANR."
User-perceived ANR rate The percentage of daily active users who experienced at least one user-perceived ANR. "Currently only ANRs of type Input dispatching timed out are considered user-perceived."
Multiple ANR rate "The percentage of your daily active users who experienced at least two ANRs."

A daily active user is one person on one device on one day, so someone who opens your app on a phone and a tablet counts twice. The data comes from devices whose users "have opted in to automatically share their usage and diagnostics data," according to the Play Console help page on crashes and ANRs.

Only the user-perceived ANR rate is a core vital, which means it "affects the discoverability of your app on Google Play." The other two are still worth watching, because background ANRs often share a root cause with the foreground ones.

A man in a pink shirt holding a phone to his ear with a pained, frustrated expression

A frozen screen for five seconds feels much longer to the person holding the phone. Photo: Andrea Piacquadio / Pexels

The ANR thresholds: 0.47% overall and 8% per device

Google publishes bad behavior thresholds for each core vital. For ANRs, from the Android vitals overview:

Core vital Overall (average across devices) Per phone model Per watch model
User-perceived ANR rate 0.47% 8% 5%
User-perceived crash rate 1.09% 8% 4%

The two thresholds trigger different penalties. Exceed the overall one and your app "is likely to be less discoverable on all devices." Exceed the per-device one and it "is likely to be less discoverable on those devices, and a warning may be shown on your store listing." That second case is easy to miss: your rate can look fine overall while one popular budget phone pulls your visibility down for everyone who owns it.

Three more rules from the same page shape how you should respond:

  • The window is 28 days. "Play uses the last 28 days of data to assess your app's quality," checking daily and using a 28-day average. When the average improves, the warnings disappear, and store listing warnings "may be removed faster if Play's system detects improvement."
  • New problems get flagged early. A crash or ANR problem that has affected devices for more than 7 days is flagged as an emerging issue, which gives you 21 days to fix it before it fills the 28-day window.
  • Memory is next. Memory usage and bitmap memory are now listed as core vitals too, and apps over those thresholds "may see store visibility impact starting from February 2027." Heavy memory pressure also makes ANRs more likely, so look at both tabs.

If your downloads dropped and you are not sure why, our guide to an app not showing in App Store or Google Play search walks through vitals as one of the checks, and Google Play ASO in 2026 covers the battery vital that sits next to this one.

How to find your ANR rate in Play Console

  1. Open Play Console and select your app.
  2. Go to Monitor and improve > Android vitals > Crashes and ANRs.
  3. Switch the view to ANRs, then use the Issue visibility filter to show foreground issues only. Those are the ones users see, and the ones the core vital counts.
  4. Sort the clusters by affected users. Google groups reports into clusters that "may share the same root cause," and its vitals page recommends fixing the largest overall clusters first.
  5. Open the per-device breakdown on the Android vitals overview and look for any phone model near 8%. A model that is far above your average usually points to an OEM-specific timeout, a slow chipset or an SDK that misbehaves on that build.
  6. Filter by app version. If the rate jumped with one release, diff that release before you dig through stack traces.

Read the ANR cluster before you change code

Every cluster has a signature, which "typically contains the top frames suspected of causing the ANR," and a type. The type tells you which timeout fired. These are the defaults Google lists for AOSP and Pixel devices; other manufacturers can differ.

ANR type Default timeout Usual cause
Input dispatching timed out 5 seconds Main thread blocked by I/O, a slow binder call, lock contention or an expensive frame
No focused window 5 seconds The app takes too long to draw its first frame
Broadcast receiver 10 seconds in the foreground, 60 in the background (10 to 20 and 60 to 120 on Android 14 and later) Slow onReceive(), or goAsync() without finish()
Executing service 20 seconds for a foreground service, 200 for a background service Slow app start or slow onCreate(), onBind() or onStartCommand()
Content provider not responding Set by the caller Slow query or exhausted binder threads
Slow job response A few seconds Slow onStartJob(), onStopJob() or setNotification()

When the stack says nativePollOnce

Many developers open their top cluster and find the main thread sitting in MessageQueue.nativePollOnce, which looks like nothing at all. Google's explanation is that the "suspected unresponsive thread was actually idle and waiting for looper messages." The usual reasons are heavy system load, a stack dump taken too late (Google measured around 100 ms on Pixels running Android 13, sometimes more than a second, and usually under 10 ms on Android 14), or a signature built from the wrong thread.

Google's advice differs by type. For input dispatch: "Ignore input dispatch ANR clusters that say 'nativePollOnce' or 'main thread idle.'" For broadcast receivers and services, do not ignore them: check worker and binder threads for deadlocks and look for code that changes views from a background thread. Clusters with no stack frames at all are not actionable from the report, so move on to the next one or capture a Perfetto trace.

A developer in glasses at a desk, reading colorful code on a laptop screen

Start with the cluster type and the top frames. They tell you which timeout fired and which thread to read. Photo: Mizuno K / Pexels

How to fix the most common causes of ANRs

Move disk and network work off the main thread

Reading a file, querying a database, decoding a large image or calling a network API on the UI thread is the classic ANR. Google's guidance is to "use withContext(Dispatchers.IO) in a ViewModel or, even better, use a Repository on the data layer":

class SettingsRepository(private val dao: SettingsDao) {
    // Callers on the main thread suspend here instead of blocking it.
    suspend fun load(): Settings = withContext(Dispatchers.IO) {
        dao.readSettings()
    }
}

The same applies to heavy computation inside Compose. Sorting or transforming a large list inside a composable runs on every recomposition; prepare the data in a ViewModel and let the UI render the result.

Remove lock contention and deadlocks

If the main thread waits on a synchronized block while another thread does slow work inside it, the main thread is blocked just as surely as if it did the work itself. Shrink the critical section, never do I/O while holding a lock the UI also needs, and consider a coroutine Mutex, which suspends instead of blocking:

class SessionStore {
    private val mutex = Mutex()
    private var cached: Session? = null

    suspend fun current(): Session = mutex.withLock {
        cached ?: fetchSession().also { cached = it }
    }
}

Keep broadcast receivers short

onReceive() runs on the main thread by default. Do the minimum there, hand anything longer than a few seconds to WorkManager, and if you use goAsync(), always call finish(). A missing finish() on an error path is one of the causes Google lists:

class SyncReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val pending = goAsync()
        CoroutineScope(Dispatchers.IO).launch {
            try {
                quickBookkeeping(context, intent) // seconds, not minutes
                WorkManager.getInstance(context)
                    .enqueue(OneTimeWorkRequestBuilder<FullSyncWorker>().build())
            } finally {
                pending.finish() // runs even when the work throws
            }
        }
    }
}

Google also notes that you can register a receiver with a handler so it runs off the main thread, and warns that a timer meant to finish a broadcast "before an ANR triggers might not work correctly."

Speed up app startup

"No focused window" and many service ANRs are really slow-startup problems. Defer SDK initialization you do not need for the first screen, avoid synchronous disk reads in Application.onCreate(), and ship a Baseline Profile so startup code is compiled ahead of time. If you build with React Native, Expo or Flutter, enabling R8 also trims startup work; our guide to fixing the DEX code optimization warning shows how.

Audit third-party SDKs

Ad, analytics and attribution SDKs often initialize on the main thread or do work during lifecycle callbacks. Unity's guide to reducing ANR rates puts it simply: "Confirm that the third party code (e.g. SDKs) you're working with are the most updated versions," because older versions "might present ANR issues." When a top cluster's frames sit inside an SDK package, update it first, then move its initialization off the startup path. If your crash reporter and Android vitals disagree, go by Android vitals: that is the number Play uses.

Stop touching views from background threads

Google's ANR guide singles out view threading violations: changing a View from a background thread can corrupt the view hierarchy's internal state and cause silent freezes, crashes and ANRs. Switch to the main thread before you update UI, with withContext(Dispatchers.Main), View.post or runOnUiThread. Google says the underlying synchronization barrier leak is fixed in Android 17, but apps "will still incur the same symptoms on Android 16 and lower," which is most of your users today.

Catch ANRs before your users do

StrictMode in debug builds. StrictMode "helps you find accidental I/O operations on the main thread while you're developing your app." Turn it on early and make it loud:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyLog()
                    .penaltyFlashScreen()
                    .build()
            )
        }
    }
}

ApplicationExitInfo in production. On Android 11 (API level 30) and later, the system keeps a record of why your process died, ANRs included, with the thread dump the system took. Read it on the next launch and send it to your own logging:

fun reportRecentAnrs(context: Context, lastSeen: Long): Long {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return lastSeen
    val am = context.getSystemService(ActivityManager::class.java)
    val anrs = am.getHistoricalProcessExitReasons(context.packageName, 0, 10)
        .filter { it.reason == ApplicationExitInfo.REASON_ANR && it.timestamp > lastSeen }
    for (exit in anrs) {
        val trace = exit.traceInputStream?.bufferedReader()?.use { it.readText() }
        Log.w("ANR", "ANR at ${exit.timestamp}: ${exit.description}\n${trace?.take(4000)}")
    }
    return anrs.maxOfOrNull { it.timestamp } ?: lastSeen
}

Store the returned timestamp so each ANR is reported once. The trace shows every thread, which often reveals the lock holder that the Play Console signature missed.

Traces and Perfetto when you can reproduce it. On a test device, enable Show all ANRs in Developer options, reproduce the problem, then pull the trace files from /data/anr with adb or capture a full adb bugreport. For the hard cases, a Perfetto trace shows whether the main thread was running or waiting, and on what.

Track the ANR rate daily with the Play Developer Reporting API

Play Console is fine for a weekly look. If you want an alert when one device model starts climbing, the Play Developer Reporting API exposes the same metrics, including userPerceivedAnrRate28dUserWeighted, the 28-day rolling average Play judges you on. Create a service account, give it access to your app in Play Console (it needs the "View app information" permission), and run:

// anr-by-device.mjs
// Usage: GOOGLE_APPLICATION_CREDENTIALS=key.json node anr-by-device.mjs com.example.app
import { GoogleAuth } from 'google-auth-library';

const pkg = process.argv[2];
if (!pkg) throw new Error('Pass your package name, for example com.example.app');

// Assumes the API returns rates as fractions (0.0047 for 0.47%). Compare one row
// with Play Console first; if the raw value already reads 0.47, set SCALE to 1.
const SCALE = 100;
const DEVICE_LIMIT = 8; // percent, per phone model

const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/playdeveloperreporting'] });
const client = await auth.getClient();

// DAILY rows are calendar days in America/Los_Angeles. endTime is exclusive,
// and the newest day may not be ready yet, so ask for the day before yesterday.
const day = (daysAgo) => {
  const d = new Date(Date.now() - daysAgo * 86_400_000);
  return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
};

const { data } = await client.request({
  method: 'POST',
  url: `https://playdeveloperreporting.googleapis.com/v1beta1/apps/${pkg}/anrRateMetricSet:query`,
  data: {
    timelineSpec: { aggregationPeriod: 'DAILY', startTime: day(3), endTime: day(2) },
    dimensions: ['deviceModel'],
    metrics: ['userPerceivedAnrRate28dUserWeighted', 'distinctUsers'],
    pageSize: 1000,
  },
});

const rows = (data.rows ?? []).map((row) => {
  const value = (name) => Number(row.metrics?.find((m) => m.metric === name)?.decimalValue?.value ?? 0);
  const device = row.dimensions?.[0];
  return {
    device: device?.valueLabel ?? device?.stringValue ?? 'unknown',
    rate: value('userPerceivedAnrRate28dUserWeighted') * SCALE,
    users: value('distinctUsers'),
  };
});

rows.sort((a, b) => b.rate - a.rate);
for (const { device, rate, users } of rows.slice(0, 20)) {
  const flag = rate >= DEVICE_LIMIT ? '  OVER THE 8% DEVICE THRESHOLD' : rate >= DEVICE_LIMIT / 2 ? '  watch' : '';
  console.log(`${rate.toFixed(2).padStart(6)}%  ${String(users).padStart(8)} users  ${device}${flag}`);
}
process.exitCode = rows.some((r) => r.rate >= DEVICE_LIMIT) ? 1 : 0;

It lists the 20 worst phone models by 28-day user-perceived ANR rate and exits with code 1 if any is over 8%, so a daily CI job or cron can post to your team chat. The distinctUsers value is rounded by Google, so use it to judge how much a model matters, not as an exact count. Swap deviceModel for versionCode to compare releases.

After the fix: how long until Play notices

Ship the fix in a staged rollout, watch the new version's ANR rate in Play Console for a few days, then complete the rollout. Because Play uses a 28-day average, the overall number falls gradually as old days drop out of the window; Google says warnings disappear when the average improves, and store listing warnings may go sooner. Write down the date each fix shipped so you can tell which change moved the line, the same habit we recommend for tracking App Store keyword ranking.

ANR rate checklist

  • User-perceived ANR rate under 0.47% overall in Android vitals.
  • No phone model at or near 8%.
  • Top foreground clusters triaged by type, with nativePollOnce input clusters set aside.
  • No disk, database or network calls on the main thread; StrictMode on in debug builds.
  • No I/O inside locks the UI thread also needs.
  • Every goAsync() path calls finish(); long work moved to WorkManager.
  • Startup work deferred, Baseline Profile in place, R8 enabled.
  • Third-party SDKs updated and initialized off the critical path.
  • ApplicationExitInfo reporting ANR traces from production.
  • A daily check of the 28-day rate per device, with an alert.

Healthy vitals, then more visibility

A low ANR rate keeps Play from holding your app back, but it does not bring people to your listing on its own. Once your vitals are green, put the app where people can find it: list your Android app on the AppRanker leaderboard, a pay-to-rank board where position is a plain sort on what each listing has paid and every row links straight to Google Play. Stable apps also earn better reviews, so pair this work with the Google Play In-App Review API and a habit of replying to app reviews. If you sell subscriptions on iOS as well, our guides to the App Store free trial and App Store price tiers cover the other half of earning from the users you keep.

FAQ

What is a good ANR rate on Google Play?

Below 0.47% of daily active users overall, and below 8% on every phone model, because those are Google's bad behavior thresholds for the user-perceived ANR rate. Most teams aim well under the line so one bad release does not push them over.

Does a high ANR rate affect ranking on Google Play?

Yes. Google says the user-perceived ANR rate is a core vital that "affects the discoverability of your app on Google Play." Above the overall threshold your app is likely to be less discoverable on all devices; above the per-device threshold, on those devices, and Play may show a warning on your listing.

What is the difference between ANR rate and user-perceived ANR rate?

The ANR rate counts users who hit any ANR, including ones in the background. The user-perceived ANR rate counts only Input dispatching timed out ANRs, the ones users notice, and it is the one Play uses for visibility.

What does nativePollOnce mean in an ANR report?

It usually means the main thread was idle, waiting for messages, when the stack was captured. For input dispatch ANRs Google says these clusters need no action; for broadcast and service ANRs, check other threads for deadlocks and look for views changed from a background thread.

How long does it take for the ANR rate to drop after a fix?

Play uses a 28-day average, so the number falls gradually as days with the old rate leave the window. Google says store listing warnings may be removed faster when its system detects improvement.

Can I get ANR data outside Play Console?

Yes. The Play Developer Reporting API returns ANR rates by device, Android version, app version and country, and ApplicationExitInfo on Android 11 and later gives your app the system's ANR trace on its next launch.

Image credits

Filed under

  • Google Play
  • Android
  • Ranking
All articles