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.
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).
- The User force-closes the app from System Settings.
- Foreground/Background ANRs occur that are inconsistently captured by standard reporters.
- Native process kills (including
SIGKILL) occur that bypass standard signal handlers.
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

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.
- Background + LMK = Gather trends. This is often the system doing its job or aggressive OEM battery management.
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.”
- Your app runs on low-end or OEM-modified devices (Xiaomi, Samsung, etc.).
- You rely heavily on background workers or native (C++) code.
🙋♂️ 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?
- Are you seeing high LMK rates on specific devices?
- Would you like me to share a snippet on how to parse the
traceInputStreamfor remote debugging?
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.
- E-book (Best Value! 🚀): $1.99 on Google Play
- Kindle Edition: $3.49 on Amazon
- Also available in Paperback & Hardcover.

Comments
Post a Comment