ANR Debugging & Prevention
📖 Concept
ANR (Application Not Responding) occurs when the main thread is blocked for too long:
- Input event: 5 seconds without response
- BroadcastReceiver.onReceive(): 10 seconds
- Service.onCreate()/onStartCommand(): 20 seconds (foreground), 200 seconds (background)
Common causes:
- Disk I/O on main thread — SharedPreferences.commit(), file reads, database queries
- Network on main thread — synchronous HTTP calls (rare now but still happens)
- Heavy computation — JSON parsing, image processing, complex algorithms
- Deadlocks — Two threads waiting for each other's locks
- Slow ContentProvider queries — Unindexed database queries
- Binder calls — Synchronous IPC to a slow system service
Debugging ANRs:
- Logcat: Look for "ANR in" messages with the process and reason
- traces.txt:
adb pull /data/anr/traces.txt— Shows stack trace of ALL threads at ANR time - StrictMode: Enable disk/network detection in debug builds
- Android Vitals (Play Console): Shows ANR rate and stack traces from production
- Perfetto: System-wide tracing for complex threading issues
Google's guideline: ANR rate should be below 0.47% (less than 1 ANR per 200 user sessions).
💻 Code Example
code
1// Common ANR causes and fixes23// ANR: SharedPreferences.commit() on main thread ❌4fun saveUserPreference(key: String, value: String) {5 prefs.edit().putString(key, value).commit() // Blocks main thread!6}7// FIX: Use apply() or DataStore8fun saveUserPreference(key: String, value: String) {9 prefs.edit().putString(key, value).apply() // ✅ Async write10}1112// Even better: Use DataStore13class PreferencesRepository @Inject constructor(14 private val dataStore: DataStore<Preferences>15) {16 suspend fun savePreference(key: String, value: String) {17 dataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value }18 }1920 fun getPreference(key: String): Flow<String?> {21 return dataStore.data.map { it[stringPreferencesKey(key)] }22 }23}2425// ANR: Database query on main thread ❌26fun getUser(id: String): User {27 return database.userDao().getById(id) // Blocks if DB is slow!28}29// FIX: Use suspend + Dispatchers.IO30suspend fun getUser(id: String): User = withContext(Dispatchers.IO) {31 database.userDao().getById(id)32}3334// ANR: Deadlock detection35// Thread A holds lock1, waits for lock236// Thread B holds lock2, waits for lock137// Both threads blocked forever — ANR if main thread is involved3839// Prevention: Always acquire locks in the same order40class SafeLocking {41 private val lock1 = ReentrantLock()42 private val lock2 = ReentrantLock()4344 // Always acquire lock1 before lock2 — prevents deadlock45 fun operation() {46 lock1.withLock {47 lock2.withLock {48 // Safe — consistent lock ordering49 }50 }51 }52}5354// StrictMode for development-time detection55if (BuildConfig.DEBUG) {56 StrictMode.setThreadPolicy(57 StrictMode.ThreadPolicy.Builder()58 .detectDiskReads()59 .detectDiskWrites()60 .detectNetwork()61 .detectCustomSlowCalls()62 .penaltyLog()63 .penaltyDeath() // Crash on violation in debug64 .build()65 )66}
🏋️ Practice Exercise
Practice:
- Enable StrictMode and find all main-thread disk/network violations in your app
- Analyze an ANR traces.txt file — identify the blocked thread and root cause
- Replace all SharedPreferences.commit() calls with apply() or DataStore
- Implement a custom ANR watchdog that detects main thread blocking
- Check your app's ANR rate on Google Play Console
⚠️ Common Mistakes
Using SharedPreferences.commit() instead of apply() — commit() writes synchronously to disk on the calling thread
Querying ContentProviders without a background thread — some providers (Contacts, MediaStore) can be slow
Not monitoring ANR rates in production — by the time users complain, your ANR rate may be critical
Running Room database operations without specifying a background dispatcher
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for ANR Debugging & Prevention. Login to unlock this feature.
Was this topic helpful?