Catching ANR-prone code before it reaches production requires a layered detection strategy: StrictMode surfaces main-thread violations in development, Macrobenchmark measures startup and frame timing in CI, Perfetto enables deep profiling, and theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/AmolPardeshi99/android-performance-skills/llms.txt
Use this file to discover all available pages before exploring further.
FrameMetricsApi monitors frozen frames in production.
StrictMode — catch violations in development and CI
StrictMode instruments the main thread at runtime and logs (or kills) the process when it performs disk reads, disk writes, or network calls on the main thread. Configure it inApplication.onCreate() so it is active from the very first frame:
penaltyLog() during local development to see violations in Logcat without crashing. Switch to penaltyDeath() in CI runs to turn any violation into a hard test failure.
Annotating known-slow calls
When a function is intentionally slow but must run on a specific thread, annotate it withnoteSlowCall so StrictMode surfaces it in violation reports:
Macrobenchmark — measure startup and frame timing in CI
The Jetpack Macrobenchmark library runs on a real or emulated device in theandroidTest module and produces reproducible measurements of startup time and frame timing. Run this in CI to catch regressions before they reach production:
StartupTimingMetric captures time-to-first-frame and time-to-fully-drawn. FrameTimingMetric captures P50, P90, P95, and P99 frame durations. Both metrics are written to a JSON output file that CI can parse and compare against baseline thresholds.
Perfetto — main thread hotspot analysis
Perfetto traces capture a full timeline of every thread slice, Binder transaction, and scheduling event. Use these SQL queries in the Perfetto UI or thetrace_processor CLI to find main-thread work exceeding one frame budget and Binder calls that block the main thread:
Duration values in Perfetto are stored in nanoseconds. Divide by
1e6 to convert to milliseconds in query output. The threshold 16000000 in the first query equals 16 ms expressed in nanoseconds.FrameMetricsApi — production frame monitoring
Window.OnFrameMetricsAvailableListener delivers per-frame timing data in production without requiring a profiler or adb connection. Register it in onStart and unregister in onStop to track frozen frames (>700 ms) and jank frames (>16 ms):
Compose compiler metrics — detect unstable parameters
The Compose compiler can emit a report listing which composables arerestartable and skippable. A composable is skippable only when all its parameters are stable — meaning Compose can skip recomposition when the parent recomposes but the parameters have not changed. A composable that is restartable but not skippable will recompose on every parent recomposition regardless of whether its inputs changed.
Enable the reports in your app module’s build.gradle.kts:
build/compose-reports/<module>-composables.txt. Each composable is listed with its stability classification. Any composable marked restartable without skippable has at least one unstable parameter — fix it by annotating the parameter type with @Immutable or @Stable, or by replacing List<T> with ImmutableList<T> from the kotlinx-collections-immutable library.
ApplicationExitInfo — post-mortem ANR analysis
ActivityManager.getHistoricalProcessExitReasons() is available from Android 11 (API 30). It returns a list of ApplicationExitInfo objects describing why the process was last terminated, including full ANR tombstone traces. Query this at app startup to detect whether the previous session ended in an ANR and to upload the trace to your observability backend:
traceInputStream contains the same thread stack and CPU data as the files in /data/anr/, but is accessible without root or a userdebug build. The importance field tells you whether the process was in the foreground or background at the time of the ANR, which helps triage user-perceived vs background ANRs.
Pulling ANR traces with adb
For local debugging on rooted or userdebug devices:Code review checklist
Use this checklist when reviewing Android code for ANR risk or when auditing an existing codebase.ANR design & architecture
ANR design & architecture
- All three ANR types are understood: Component-class (AMS), Input-class (InputDispatcher), No-Focused-Window
- Foreground service calls
startForeground()within 3 s (API 35+) or 5 s (API 26–34) ofonStartCommand ApplicationExitInfois queried at startup to capture ANR traces (Android 11+ / API 30+)- Production crash reporter uploads ANR trace bytes with
timestamp,description, andimportancefields - ANR monitoring dashboard tags OEM-freeze false positives by detecting
unfreeze … reason: Signalin logs within 1–2 s ofam_anr
View-based system
View-based system
- No network, file, database, or
ContentResolvercalls run onDispatchers.MainorDispatchers.Default - All JSON/Protobuf parsing runs on
Dispatchers.IOorDispatchers.Default— never afterobserveOn(mainThread()) - No
runBlockingin Activity, Fragment, ViewModel, or Service code - No
GlobalScope.launchin production — use an injected application-scopedCoroutineScope Dispatchers.IOfor all I/O;Dispatchers.Defaultfor CPU work;Dispatchers.Mainfor UI onlylazyon Main-only UI properties usesLazyThreadSafetyMode.NONE- No
synchronized {}block contains asuspendcall — useMutex.withLockinstead ReentrantLockalways useswithLock {}ortry/finally; never held across a suspend point- Nested lock acquisitions follow a consistent, documented ordering; no cross-thread lock inversion
- Crash, logging, and analytics utilities do not hold their own locks while calling into other locked utilities
StrictModeenabled in debug builds; zero disk/network violations on the main threadonBindViewHoldercontains only view-assignment — no filtering, mapping, or I/ODiffUtilcalculated off Main (orListAdapterused)onDrawcontains no object allocationsBroadcastReceiver.onReceiveusesgoAsync()only for work under 10 s, or delegates to WorkManagergoAsync()PendingResult.finish()is always called — including on the error path — before the timeoutJobService.onStartJobreturns immediately and performs work in a coroutine- Services call
stopSelf()promptly after work completes - SharedPreferences reads on
Dispatchers.IO; writes useapply(), nevercommit(); consider DataStore migration - Cancellation checkpoint (
ensureActive()oryield()) present in tight CPU loops - Binder calls to system services (
PackageManager,AccountManager,ContentResolver) are off Main - Heavy startup work is deferred past the first frame using
window.decorView.post { } - Bitmap decoding is scaled to display size before being handed to the RenderThread
Jetpack Compose system
Jetpack Compose system
- No expensive computation directly in the composable body — use
remember(key)or push to a ViewModel - No backwards state write (writing state after reading it in the same composition phase)
- High-frequency state reads (scroll offset, animation value) deferred via lambda-based Modifiers or
derivedStateOf derivedStateOfused for state derived from a rapidly-changing source to avoid over-recomposition- All composable parameters are stable (
@Immutable,@Stable, primitives, or Immutable Collections) LazyColumn/LazyRowitems have stablekey = { item.id }- No
runBlockingor blocking calls inside a composable body — useLaunchedEffect - Side effects (Toast, analytics, network) are inside
LaunchedEffect, not the composable body Modifier.offset { }(lambda form) used instead ofModifier.offset(value)for scroll-driven offsets- Compose compiler metrics checked in CI — all composables expected to be
restartable skippableare - FrameMetrics or Android Vitals monitored for frozen frame rate at or below acceptable threshold
ANR analysis readiness
ANR analysis readiness
- Debug builds capture and log
ApplicationExitInfoANR traces on next launch - Production observability backend receives ANR trace bytes, not just stack strings
- Team can read thread state (
BlockedvsNativevsTimedWaiting) in trace files - Team can interpret CPU load lines and page fault counts in
"ANR in"log entries - Perfetto trace is captured for any ANR not fully explained by thread stack alone