adb logcat *:E | grep -E "FATAL|ANR|Exception"Exception or Throwable — this masks root causes and creates silent data corruption.Throwable or Error in production — only catch specific Exception subclasses. Catching OutOfMemoryError leads to undefined behavior.TransactionTooLargeException has a hard 1MB Binder buffer limit per process — this cannot be increased.| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | NullPointerException | ~35% | java.lang.NullPointerException: Attempt to invoke virtual method '...' on a null object reference | Use Kotlin null safety (?., ?:); annotate Java with @NonNull/@Nullable |
| 2 | ANR — main thread I/O | ~15% | ANR in com.example.app + main thread in WAITING/BLOCKED | Move all disk/network I/O to coroutines (Dispatchers.IO) or WorkManager |
| 3 | OutOfMemoryError | ~10% | java.lang.OutOfMemoryError: Failed to allocate a N-byte allocation | Use Glide/Coil for images; set inSampleSize; avoid static bitmap references |
| 4 | IllegalStateException (Fragment) | ~8% | IllegalStateException: Can not perform this action after onSaveInstanceState | Use commitAllowingStateLoss() or check lifecycle.currentState.isAtLeast(STARTED) |
| 5 | SecurityException | ~6% | SecurityException: Permission Denial: ... requires android.permission.X | Check ContextCompat.checkSelfPermission() before calling protected APIs |
| 6 | SIGSEGV (native) | ~5% | signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 | Use AddressSanitizer; upload native debug symbols; check for use-after-free |
| 7 | TransactionTooLargeException | ~4% | TransactionTooLargeException: data parcel size N bytes | Keep onSaveInstanceState bundles < 500KB; use ViewModel for large state |
| 8 | IndexOutOfBoundsException | ~4% | IndexOutOfBoundsException: Index: N, Size: M | Validate list sizes; use getOrNull(); synchronize concurrent list modifications |
| 9 | DeadObjectException | ~3% | android.os.DeadObjectException at IPC call site | Wrap Binder calls in try/catch; reconnect on failure |
| 10 | ClassCastException | ~2% | ClassCastException: X cannot be cast to Y | Use is checks; prefer generics; use typed Bundle getters |
| 11 | SIGABRT (native) | ~2% | signal 6 (SIGABRT), code -6 (SI_TKILL) + FORTIFY: ... | Fix buffer overflows; review assert() conditions |
| 12 | ConcurrentModificationException | ~2% | ConcurrentModificationException during iteration | Use CopyOnWriteArrayList or toMutableList() before modification |
START
├── Is it an ANR (dialog says "App isn't responding")?
│ ├── YES → Check main thread state in traces.txt
│ │ ├── Main thread BLOCKED on I/O → Move to Dispatchers.IO (Cause #2)
│ │ ├── Main thread WAITING on lock → Fix lock contention or deadlock
│ │ └── Main thread in long computation → Move to Dispatchers.Default
│ └── NO ↓
├── Is it a Java/Kotlin exception?
│ ├── YES → Read the exception class name
│ │ ├── NullPointerException → Cause #1: check null safety
│ │ ├── OutOfMemoryError → Cause #3: profile heap with Android Profiler
│ │ ├── IllegalStateException → Cause #4: check fragment/activity lifecycle
│ │ ├── SecurityException → Cause #5: check runtime permissions
│ │ ├── TransactionTooLargeException → Cause #7: reduce bundle size
│ │ ├── IndexOutOfBoundsException → Cause #8: validate bounds
│ │ ├── DeadObjectException → Cause #9: handle IPC failures
│ │ ├── ClassCastException → Cause #10: use type checks
│ │ └── ConcurrentModificationException → Cause #12: synchronize access
│ └── NO ↓
├── Is it a native crash (signal in logcat)?
│ ├── YES → Read the signal number
│ │ ├── SIGSEGV (signal 11) → Cause #6: null ptr / use-after-free in C/C++
│ │ ├── SIGABRT (signal 6) → Cause #11: assertion failure / buffer overflow
│ │ ├── SIGBUS → Misaligned memory access in native code
│ │ └── SIGFPE → Division by zero in native code
│ └── NO ↓
└── DEFAULT → Enable StrictMode + Crashlytics; reproduce with adb logcat filtering
Connect the device via USB and start logcat with crash filtering. [src1]
# Filter for fatal crashes and ANRs
adb logcat *:E | grep -E "FATAL EXCEPTION|ANR in|signal [0-9]+"
# Save full logcat to file for analysis
adb logcat -d > crash_log.txt
Verify: adb logcat -d | grep "FATAL EXCEPTION" → shows crash with full stack trace, thread name, PID.
Parse the first line of the exception to determine the crash type. Match against the Quick Reference table. [src7]
# Java/Kotlin crash format:
FATAL EXCEPTION: main
Process: com.example.app, PID: 12345
java.lang.NullPointerException: Attempt to invoke virtual method
'int java.lang.String.length()' on a null object reference
at com.example.app.MainActivity.processData(MainActivity.kt:42)
# Native crash format:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 12345, tid: 12345, name: main >>> com.example.app <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Verify: You can identify the exception class, exact file/line number, and thread name from the trace.
For crashes involving fragments, activities, or configuration changes, verify the component lifecycle state. [src6]
// Add lifecycle logging to narrow down timing
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val data = withContext(Dispatchers.IO) { repository.fetchData() }
if (viewLifecycleOwner.lifecycle.currentState
.isAtLeast(Lifecycle.State.STARTED)) {
binding.textView.text = data
}
}
}
}
Verify: adb logcat | grep "Lifecycle" — crash should not occur after DESTROYED state.
Use Android Studio Profiler to capture heap dumps and track allocations. [src5]
# Dump heap from command line
adb shell am dumpheap com.example.app /data/local/tmp/heap.hprof
adb pull /data/local/tmp/heap.hprof
# Check process memory limits
adb shell getprop dalvik.vm.heapsize
Verify: Open .hprof in Android Studio Memory Profiler — look for retained Bitmap and byte[] allocations.
Pull the ANR trace files to analyze which thread is blocked. [src2]
# List ANR trace files
adb shell ls /data/anr/
# Pull the most recent trace
adb pull /data/anr/traces.txt
# On Android 11+, use ApplicationExitInfo
adb shell dumpsys activity exit-info com.example.app
Verify: In traces.txt, find the main thread — its state (BLOCKED, WAITING) reveals the ANR cause.
Use ndk-stack to convert raw addresses to source locations. [src3]
# Symbolicate from logcat output
adb logcat | ndk-stack -sym path/to/obj/local/arm64-v8a/
# From tombstone file
adb pull /data/tombstones/tombstone_00
ndk-stack -sym path/to/obj/local/arm64-v8a/ -dump tombstone_00
Verify: Output shows source file paths and line numbers instead of raw hex addresses.
// Input: Nullable data from Intent extras, Bundle, or Java interop
// Output: Crash-free access with sensible defaults
// Safe call + Elvis for default value
val userName: String = intent.getStringExtra("user_name") ?: "Guest"
// let-block for conditional execution
intent.getStringExtra("deep_link")?.let { url ->
navigator.handleDeepLink(url)
}
// require for fail-fast with clear messages
fun processOrder(orderId: String?) {
requireNotNull(orderId) { "orderId must not be null" }
repository.loadOrder(orderId) // smart-cast to non-null
}
// Input: Async operations that outlive the Activity/Fragment
// Output: Automatic cancellation, no IllegalStateException
class SearchFragment : Fragment(R.layout.fragment_search) {
private val viewModel: SearchViewModel by viewModels()
override fun onViewCreated(view: View, s: Bundle?) {
super.onViewCreated(view, s)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { updateUI(it) }
}
}
}
}
// Input: Runtime permission request before camera access
// Output: Graceful handling without SecurityException crash
private void openCamera() {
if (ContextCompat.checkSelfPermission(this, CAMERA)
== PackageManager.PERMISSION_GRANTED) {
launchCamera();
} else if (shouldShowRequestPermissionRationale(CAMERA)) {
showPermissionRationale();
} else {
requestPermissions(new String[]{CAMERA}, REQ_CAMERA);
}
}
// Input: Large image file path
// Output: Downsampled bitmap that fits in memory
public static Bitmap decodeSampledBitmap(String path,
int reqW, int reqH) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
opts.inSampleSize = calcSampleSize(opts, reqW, reqH);
opts.inJustDecodeBounds = false;
opts.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(path, opts);
}
// BAD — hides real bugs, causes silent data corruption
try {
val result = processPayment(order)
updateUI(result)
} catch (e: Exception) {
// Swallowed — no logging, no user feedback
}
// GOOD — catches expected failures, lets real bugs surface
try {
val result = processPayment(order)
updateUI(result)
} catch (e: IOException) {
Log.w(TAG, "Network error", e)
showRetryDialog()
} catch (e: PaymentDeclinedException) {
showDeclinedMessage(e.reason)
}
// BAD — causes ANR after 5 seconds
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val response = URL("https://api.example.com/data").readText()
binding.textView.text = response
}
// GOOD — non-blocking, lifecycle-aware
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val response = withContext(Dispatchers.IO) {
URL("https://api.example.com/data").readText()
}
binding.textView.text = response
}
}
// BAD — leaks the entire Activity, leads to OOM
companion object {
var currentActivity: Activity? = null
val cachedBitmap: Bitmap? = null
}
// GOOD — ViewModel survives config changes without leaking
class MainViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
viewModelScope.launch { _data.value = repository.fetchData() }
}
}
// BAD — crashes with IllegalStateException
fun onDataLoaded(data: Data) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailFragment.newInstance(data))
.commit() // CRASH if after onSaveInstanceState
}
// GOOD — guards against state loss
fun onDataLoaded(data: Data) {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailFragment.newInstance(data))
.commit()
} else {
pendingFragment = DetailFragment.newInstance(data)
}
}
String!) — these bypass null safety. Fix: Add @Nullable/@NonNull annotations to all public Java APIs. [src1]StrictMode.setThreadPolicy(Builder().detectAll().penaltyLog().build()) in debug builds. [src2]a.b.c.d()). Fix: Upload mapping.txt to Play Console and Crashlytics for every release. [src7]TransactionTooLargeException. Fix: Store only IDs in bundles; use ViewModel or SavedStateHandle for large state.-memory 2048. [src5]kotlinx.coroutines. [src2]RGB_565 for opaque images (50% memory savings). [src5]# Capture crash logcat in real time
adb logcat *:E
# Filter for fatal exceptions only
adb logcat | grep -E "FATAL EXCEPTION|Process.*PID"
# Check ANR traces
adb shell ls -la /data/anr/
adb pull /data/anr/traces.txt ./anr_traces.txt
# Dump current process memory stats
adb shell dumpsys meminfo com.example.app
# Check memory limits on device
adb shell getprop dalvik.vm.heapsize
adb shell getprop dalvik.vm.heapgrowthlimit
# Monitor GC activity in real time
adb logcat -s "art" | grep -i "gc"
# Get application exit reasons (Android 11+)
adb shell dumpsys activity exit-info com.example.app
# Symbolicate native crash from tombstone
ndk-stack -sym path/to/obj/local/arm64-v8a/ -dump tombstone_00
# List tombstone files from native crashes
adb shell ls -la /data/tombstones/
# Capture a bug report with full system state
adb bugreport > bugreport.zip
| Android Version | Status | Crash-Related Changes | Migration Notes |
|---|---|---|---|
| Android 15 (API 35) | Preview | Stricter background process limits; tombstone format improvements | Test background services under new restrictions |
| Android 14 (API 34) | Current | Foreground service type required; stricter implicit intents | Add foregroundServiceType to manifest |
| Android 13 (API 33) | Supported | Runtime notification permission; per-app language | Add POST_NOTIFICATIONS permission |
| Android 12 (API 31) | Supported | Tombstone collection via Crashlytics NDK; strict exported flag | Set android:exported on all intent-filter components |
| Android 11 (API 30) | Supported | ApplicationExitInfo API; AsyncTask deprecated | Use getHistoricalProcessExitReasons() |
| Android 10 (API 29) | Maintenance | Scoped storage; background activity launch restrictions | Update file access; use PendingIntent for background launches |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Debugging production crash reports from Crashlytics/Play Console | Crash is a known SDK bug with a pending fix | Wait for SDK update; add workaround |
| Setting up crash prevention patterns in a new project | Issue is a build error or compilation failure | Standard build error debugging |
| Profiling ANR rates during performance optimization | Issue is slow rendering (jank) without ANR | Android GPU Inspector or Frame Profiler |
| Investigating OOM on low-memory devices | Memory is high but no actual crash occurs | Memory Profiler for optimization |
| Training team on crash-free coding practices | Crash only occurs in unit tests | Check test framework and mocking setup |
/data/anr/traces.txt) may be overwritten by subsequent ANRs — pull them immediately.TransactionTooLargeException is only thrown on Android 7.0+; earlier versions silently fail.