The Silent Killers: How to Debug Android App Exits You Never Knew Happened

Stop relying on Crashlytics alone. Learn how to use ApplicationExitInfo to track LMKs, silent ANRs, and native process kills.

The Silent Killers: How to Debug Android App Exits You Never Knew Happened

 Every Android developer has been there: you look at your Google Play Console, and the crash rate looks “clean.” But then you check your reviews, and users are complaining that the app “just closes” or “restarts randomly.”

The truth is, many app terminations happen in the shadows. Whether it’s a system-level process kill due to low memory or a user force-stopping the app, these events often bypass traditional crash reporters like Firebase Crashlytics.

Thankfully, since Android 11, we have a specialized diagnostic tool: ApplicationExitInfo.

Why Standard Crash Reporting Leaves You Blind

Most crash SDKs rely on Thread.setDefaultUncaughtExceptionHandler. This works for JVM-level fatal exceptions, but it is effectively blind when:

  • The OS kills your process to reclaim RAM (Low Memory Killer).

Introducing ApplicationExitInfo

This API, introduced in Android 11 (API 30), allows you to query the system for the specific reason your app’s process died the last time it was running.

Common Exit Reasons at a Glance

Press enter or click to view image in full size
Common Exit Reasons at a Glance
Common Exit Reasons at a Glance

The Implementation (Kotlin)

The best practice is to check this once during app startup — specifically in your Application class or your entry Activity.

import android.app.ActivityManager
import android.app.ApplicationExitInfo
import android.content.Context
import android.os.Build
import android.util.Log

fun checkLastExitReason(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

// Fetch the most recent historical exit reason
val exitList = am.getHistoricalProcessExitReasons(context.packageName, 0, 1)

if (exitList.isNotEmpty()) {
val lastExit = exitList[0]

// Context matters: Was the app in the foreground when it died?
val wasForeground = lastExit.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND

when (lastExit.reason) {
ApplicationExitInfo.REASON_ANR -> Log.e("ExitDebug", "Silent ANR. Foreground: $wasForeground")
ApplicationExitInfo.REASON_LOW_MEMORY -> Log.w("ExitDebug", "Low Memory Kill (LMK) detected.")
ApplicationExitInfo.REASON_USER_REQUESTED -> Log.i("ExitDebug", "User force-stopped the app.")
ApplicationExitInfo.REASON_SIGNALED -> Log.e("ExitDebug", "Native process kill detected.")
else -> Log.d("ExitDebug", "Reason: ${lastExit.description}")
}

// traceInputStream is usually available for ANRs and native crashes
lastExit.traceInputStream?.let { inputStream ->
// Upload for remote post-mortem analysis
}
}
}
}

Technical Nuances

1. Process Importance: When to Panic?

Always check lastExit.importance. This helps you prioritize your bug-fixing roadmap:

  • Foreground + LMK = Investigate immediately. Your app is likely leaking memory or using massive assets.

2. Swipe-away vs. Force-stop

Swiping an app out of “Recents” doesn’t always trigger REASON_USER_REQUESTED. Depending on the device, it might show up as REASON_OTHER or not be recorded if the process was already cached.

3. Analytics Best Practice

To avoid duplicate noise in your dashboards, log exit reasons only once per cold start (or once per unique process ID).

Who is this for?

This approach is a game-changer if:

  • Your crash rate looks clean, but user reviews complain about “random closures.”

🙋‍♂️ Frequently Asked Questions (FAQs)

Does this work on older Android versions?

No, this is an Android 11+ feature. Older versions are still limited to standard crash reporting.

Does this impact app performance?

Not at all. You are simply reading a historical log that the system already maintains.

Can I see the exact line of code for a Low Memory kill?

No. An LMK is an external system action. However, the description field provides context on the system state at the time.

💬 Let’s Discuss!

  • Have you ever been haunted by a bug that didn’t show up in Crashlytics?

Drop a comment below and let’s get those stability metrics to 100%!

📘 Master Your Next Technical Interview

Since Java is the foundation of Android development, mastering DSA is essential. I highly recommend “Mastering Data Structures & Algorithms in Java”. It’s a focused roadmap covering 100+ coding challenges to help you ace your technical rounds.

Comments

Popular posts from this blog

Why You Should Stop Passing ViewModels Around Your Compose UI Tree 🚫

Coroutines & Flows: 5 Critical Anti-Patterns That Are Secretly Slowing Down Your Android App

Stop Writing Massive when Statements: Master the State Pattern in Kotlin