androidengineers.Book a session

Monitoring & Analytics

Crash/ANR Analysis & Signals

article25 minMedium

Crashes and ANRs (Application Not Responding) are the clearest signals of app quality. Understanding how to read crash reports and identify ANR root causes lets you fix the right things first.

Crash Reporting with Firebase Crashlytics

Setup

// build.gradle.kts (app)
plugins {
    id("com.google.gms.google-services")
    id("com.google.firebase.crashlytics")
}

// dependencies
implementation("com.google.firebase:firebase-crashlytics-ktx")

Enriching Crash Reports

class AppCrashReporter @Inject constructor(
    private val crashlytics: FirebaseCrashlytics
) {
    fun setUser(userId: String) {
        crashlytics.setUserId(userId)
    }

    fun setContext(key: String, value: String) {
        crashlytics.setCustomKey(key, value)
    }

    fun logBreadcrumb(message: String) {
        crashlytics.log(message)  // appears in crash report's log section
    }

    fun recordNonFatal(throwable: Throwable) {
        crashlytics.recordException(throwable)
    }
}

// In ViewModel
fun loadArticle(id: String) = viewModelScope.launch {
    reporter.setContext("article_id", id)
    reporter.logBreadcrumb("Loading article $id")
    try {
        val article = repository.getArticle(id)
        reporter.logBreadcrumb("Article loaded: ${article.title}")
        _state.value = UiState.Success(article)
    } catch (e: Exception) {
        reporter.recordNonFatal(e)
        _state.value = UiState.Error(e.message ?: "Unknown error")
    }
}

Crash-Free Rate Target

Crash-free sessions: > 99.5% (industry standard for high-quality apps)
Crash-free users: > 99.9%

Alert threshold: crash rate > 0.1% → page on-call

Reading a Crash Report

A Crashlytics report contains:

  1. Exception class + message — the actual error
  2. Stack trace — where in the code it happened
  3. Custom keys — your context (user_id, screen, etc.)
  4. Logs (breadcrumbs) — what the user was doing before the crash
  5. Device info — OS version, RAM, storage, device model
  6. Session info — time since app start, foreground/background

ANR Analysis

ANRs happen when the main thread is blocked for > 5 seconds (input dispatch) or > 10 seconds (broadcast receivers).

Common ANR Causes

// BAD: Network call on main thread
fun loadUser(id: String): User {
    return api.getUser(id)  // BLOCKS main thread → ANR
}

// BAD: Long lock on main thread
val lock = ReentrantLock()
fun onClickButton() {
    lock.lock()  // if lock held by background thread → deadlock → ANR
    try { /* ... */ }
    finally { lock.unlock() }
}

// BAD: SharedPreferences.apply() called synchronarily
override fun onStop() {
    prefs.edit().putString("key", "value").commit()  // commit() = sync disk I/O
}

// GOOD: Use coroutines to move work off main thread
fun loadUser(id: String) = viewModelScope.launch {
    val user = withContext(Dispatchers.IO) { api.getUser(id) }
    _state.value = UiState.Success(user)
}

Reading the ANR Trace

Play Console → Android Vitals → ANRs shows the main thread stack trace at the time of ANR:

// Typical lock contention ANR:
at java.lang.Object.wait(Native Method)
at java.lang.Thread.parkFor$(Thread.java:1235)
at sun.misc.Unsafe.park(Unsafe.java:299)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
at com.myapp.ArticleCache.get(ArticleCache.kt:42)   ← your code

The key line is the first one from your package: com.myapp.ArticleCache.get holding a lock while the main thread waits.

Android Vitals Thresholds

MetricBadGood
Crash rate> 1.09%< 0.5%
ANR rate> 0.47%< 0.2%
Slow renders> 50% frames slow< 20%
Frozen renders> 0.1% frames frozen< 0.05%
Startup time> 5s cold< 2s cold

Custom Traces for Non-Crash Errors

class ArticleApiMonitor(private val crashlytics: FirebaseCrashlytics) {
    suspend fun fetchWithMonitoring(id: String): Article {
        return try {
            api.getArticle(id)
        } catch (e: HttpException) {
            // Non-fatal: 404 is expected; 5xx is unexpected
            if (e.code() >= 500) {
                crashlytics.setCustomKey("http_status", e.code())
                crashlytics.recordException(e)
            }
            throw e
        }
    }
}

Key Takeaways

SignalWhat to watch
Crash-free rateTarget > 99.5%; alert if drops below 99%
Custom keysAlways set user_id, screen_name, last_action before sensitive operations
BreadcrumbsLog user actions; crucial for reproducing rare crashes
ANR root causeMain thread lock contention; sync I/O; blocking coroutine
Non-fatalsTrack expected errors (timeouts, 4xx) separately from bugs
Android VitalsGoogle uses these to surface your app in Play Store; below threshold = featured penalty

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Crash/ANR Analysis & Signals | Android System Design | Android Engineers