← Back to list

🧠 Engineering Resilient Android Apps: ANRs, Crashes & The Art of Not Breaking Production

Megha kumari · 2026-03-26 18:56 · 1 claps · 6.9 min read
#android-app-development #app-crashing #anr #kotlin #firebase-crashlytics
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

🧠 How to resolve ANRs & Crashes in Android apps

Developer debugging ANRs

Developer debugging ANRs

If you’re an Android developer trying to figure out why your app crashes or freezes… welcome. You’re in the right debugging dungeon.

During development, everything feels smooth. Your app runs perfectly on your device. Life is good. Then you release it. And suddenly:

  • Users on slow networks 📡
  • Low RAM devices 🧠
  • Old Android versions 🧓
  • Background restrictions 🚫

…start exposing things your local testing politely ignored.

And boom 💥

  • ANR (Application Not Responding)
  • Crashes

At that point, your app isn’t just misbehaving… It’s filing complaints against your architecture 😄

yes, if your code is blocking the main/UI thread and it’s taking more than 5 seconds to complete, your app will crash.

Below are the few examples

  1. You hit an API on the main thread to fetch dashboard data from the server and it took more than 5 seconds to complete which is a lot of time obviously in execution.(why? slow internet in rural areas/basement or slow API response when data set is large)

  2. You run some time taking room db or SQLite queries on the main thread.

  3. You have some code inside broadcast receiver which took more than 10 seconds like some API call or db query.

  4. Using shared preferences a lot to read and write values could get you in trouble. your prefs data is stored locally and it has a limit too.

  5. You have a toast in an error condition which you didn’t test in happy path testing. but you tried to run it on IO thread instead of main thread.

  6. you tried to show a dialog/toast to the user but before the execution completes, user moved to other activity/fragment/some other app or closed the app. now the toast is looking for context and it didn’t get any so crash!!.

  7. using delay to run some code after 5 seconds. It blocked the main thread.

So there are tons of scenarios where things could go wrong. So what do you need to focus on?

  1. Use Firebase crashlytics tool in your app. It’s a simple configuration. It will catch all ANRs and crashes, their stack traces, device details in their logs which you can easily check in the firebase console crashlytics dashboard.
  2. Check play store crashes logs after release and closely monitor it
  3. Use Android Profiler to check CPU and memory usage by individual files
  4. Use strictMode in debug environment. It will help a lot to see memory related issues early.

🚨 Understanding the Failure Modes

🧊 ANR (Application Not Responding)

🧊 ANR (Application Not Responding)

ANR is Android saying:

“You blocked the main thread. I waited. You didn’t respond. I’m done.”

⏱️ Time Limits

  • ~5 seconds → Input events
  • ~10 seconds → BroadcastReceiver

If exceeded → ANR dialog appears

Let’s understand with the below examples where things usually go wrong.

🧪 This code “Looks Fine in Dev” Trap

val data = apiThread.join() // ❌ blocking main thread
showData(data)

How will this perform on WiFi:

Smooth like butter 🧈(like new phase of a relationship)

BUTTTTT On real-world network:

Frozen like your career choices during appraisals 😄

🛑 Golden Truths (Tattoo These Mentally)

  • ❌ Main(UI) thread is NOT a worker (don’t assign heavy work to it)
  • join(), wait(), get() = silent ANR generators
  • ❌ Long locks = future regret (check every function that could create a lock for a long time like sleep())

If your main thread is waiting… your users are leaving 🚪

💥 Crash: Deterministic Failures

Crashes are explicit failures — usually due to Invalid state, Lifecycle misuse or Unhandled edge cases. But in modern apps, many crashes are timing-related rather than logic-related.

🧪 Real Production Favorite

java.lang.IllegalStateException: Fragment not attached to a context

Translation:

“You’re trying to talk to someone who already left the chat.” 📵

🧠 What Actually Happened?

  • API call started
  • User navigated away
  • Fragment destroyed
  • Response came back late (API is still running in process)
  • UI tried to update(Code inside success/error callbacks executes)

💥 Boom. App Crashed. e.g. show a toast/dialog on a killed fragment

✅ Fix: Lifecycle Awareness

Manual Check

if (isDestroyed || isFinishing) return

Better Approach (Lifecycle-aware coroutines)

viewLifecycleOwner.lifecycleScope.launch {
    val data = apiCall()
    textView.text = data.name
}

✔ Auto cancellation ✔ No invalid UI updates ✔ No crash

Lifecycle-aware code = emotionally mature code 😄

🛠️ Android Profiler: Your App’s Lie Detector

🔍 How to Open Android Profiler

  1. Run your app on emulator/device
  2. Go to View → Tool Windows → Profiler
  3. Select your app process

Boom. You’re inside the control room 🎛️

It shows:

  • CPU usage 🔥
  • Memory leaks 🧠
  • Network delays 🌐

Basically:

“Here’s what your app is actually doing, not what you think it’s doing.” 😏

It helps answer:

  • Where is time being spent?
  • What is blocking the main thread?
  • How is memory evolving over time?

📊 CPU Profiler: Catching the Main Thread Villain

🚨 Usual Suspects

val data = apiCall() // ❌
val users = userDao.getAllUsers() // ❌

Main thread:

“I was hired for UI… why am I doing database labor?” 😭 I can’t leave the user waiting for your bad coding standards. So, I will make the app crash after 5 seconds timer beeps.

✅ Fix

Using coroutines to run the asynchronous code in the IO thread so the main thread remains free for other tasks. It’s like delegating your job to some other colleague when you are overloaded so you can take care of other responsibilities. Otherwise, you will get sick.

CoroutineScope(Dispatchers.IO).launch {
    val data = apiCall()//asynchronous code
}
CoroutineScope(Dispatchers.IO).launch {
    val users = userDao.getAllUsers()
}

So, now Android will run the code on a new thread chosen from the Thread pool. once, it gets completed the thread gets free & waits for new task in the pool.

🗄️ DB Reality Check

  • ❌ Querying DB on main thread = ANR invitation
  • ❌ Fetching entire table = overconfidence
  • ✅ Pagination = maturity
  • ✅ Flow = elegance

Your database is not fast. It’s just pretending during testing because you tested it on little set of records on a new high RAM device😄

Why the end user struggles?

so the same code which is running fine on Android 16 will create issues on Android 10 low memory devices and issue will not be reproducible on your device or any good memory device. So, you have to think in all direction.

📡 Broadcast Receiver: The 10-Second Deadline

Android System:

“Handle this event quickly.”

Your code:

“Let me fetch data, process it, upload it…”

Android:

“Absolutely not.” 🚫

❌ Anti-Pattern

onReceive is not designed to run DB queries or making API calls

//Inside a broadcast receiver
override fun onReceive(...) {
    fetchLargeDataFromDb()
    uploadToServer()
}

✅ Correct Thinking

Use background/foreground service

context.startService(Intent(context, MyService::class.java))

Or better:

  • WorkManager- Delegate job to other thread

BroadcastReceiver is not a worker. It’s a messenger. Don’t make it do manual labor. It just came to say hey here is your new location updates or here is your 3rd party API response.

🧩 Third-Party SDKs: Trusted… But Verified

If you are using some 3rd party SDK/library, you just can’t see what’s happening inside a method provided by the SDK. Is it running some synchronous code or some foreground service internally? Is it makingAPI calls to save your data?

ThirdPartySdk.trackEvent("USER_LOGIN")

Looks innocent.

Internally:

  • Network call 🌐
  • Serialization 📦
  • Timeout ⏳
  • Drama 🎭

Your app is only as fast as the slowest SDK you integrate.

Rule

Always assume:

“This SDK might ruin my day.” Play safe because you never know.

CoroutineScope(Dispatchers.IO).launch {
    ThirdPartySdk.trackEvent("USER_LOGIN")
}

🧠 Memory Profiler: The Hoarding Problem

Memory leaks are like:

“I might need this Activity later… so I’ll keep it forever.” 🧟

Be careful where you are using activity context or application context and for how long. Will it be handled by GC? Maybe not! Depends.

❌ Classic Mistake

companion object {
    var context: Context? = null
}

Congratulations 🎉 You just leaked an entire Activity.

🔍 Symptoms

  • App slows down
  • RAM usage climbs
  • Eventually: 💥 BOOM

Garbage collector is not your maid. Clean up your own mess 😄

🔁 Looper & Message Queue: The Traffic System

Main thread = single-lane highway 🚗One truck stops…Everything behind it:

“Guess we live here now.” 🚗😐🚗🚗🚗🚗

Disaster Code

runOnUiThread {
    Thread.sleep(5000)
}

You didn’t just block the thread…

You froze time ⏳

🍞 Toast Problems (Yes, Even Toast)

Thread {
    Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
}.start()

Crash:

“No Looper found. Please try again.” 😄

Or worse:

  • 1000 Toasts in loop
  • Message queue flooded

Congratulations, you DDoS-ed your own UI.

🌐 Network: The Silent Villain

🧪 The Classic Hidden Problem

val response = apiService.getData() // ❌ no timeout handling

Looks fine… until:

  • Server is slow
  • Request hangs
  • Coroutine never completes
  • UI keeps waiting

Meanwhile:

Your loading spinner deserves a salary for overtime 🌀

🔍 What to Watch For

  • 🐢 Slow APIs (high response time)
  • 🔁 Infinite or poorly designed retries
  • 📦 Large payloads (JSON the size of a novel)
  • 🔗 Sequential dependent API calls
  • 📡 Poor network conditions (real users ≠ office WiFi)

Thread blocking + user frustration = ANR risk

✅ The Right Way

✔️ Use Timeouts

OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)

✔️ Limit Retries

Retry with:

  • Backoff strategy
  • Retry count limit

🎯 Pro Tip

Network latency is where architecture gets exposed.

In local testing:

Everything is fast. Everyone is happy.

In production:

2G network + slow server + impatient user = reality check 📉

🧾 Logcat: The Truth Serum

If you’re not logging:

You’re not debugging. You’re guessing.

🚑 Crash Handling (Like a Responsible Adult)

✔️ Null Safety

val name = user?.name ?: "Guest"

✔️ Controlled Exception Handling

Don’t wrap everything in try-catch like:

“If I don’t see the error, it doesn’t exist.” 😄

🔥 Crashlytics

Tells you:

  • Where it crashed
  • Why it crashed
  • How many users are judging you

Production users are the best testers… unfortunately 😅

🧊 ANR Prevention Philosophy

  • Main thread = sacred
  • Async everything
  • Respect lifecycle
  • Design for failure

🧙‍♂️ Advanced Tools

  • StrictMode → calls out your bad habits(For Dev Env. only)
  • ANR traces → post-mortem analysis
  • LeakCanary → catches leaks before users do

🎯 Final Thoughts

At senior level, debugging is not:

“Fix this crash”

It’s:

“Why was this even possible?”

ANRs = threading design flaws Crashes = state/lifecycle violations

🧠 Closing Thought

Great apps don’t just work. They fail gracefully… and rarely.

😄 One Last Truth Bomb

Why did the main thread quit?

Because:

“Everyone kept giving me work… but nobody let me respond.” 😭

🚀 Keep your threads light, your UI responsive, and your production logs boring. Don’t forget to clap 👏if this helped. Plaese share your reviews. This is my first blog.


메타데이터
post_id
c3d77ad61ebd
slug
engineering-resilient-android-apps-anrs-crashes-the-art-of-not-breaking-production-c3d77ad61ebd
url
https://medium.com/@meghakumari2203/engineering-resilient-android-apps-anrs-crashes-the-art-of-not-breaking-production-c3d77ad61ebd
canonical_url
https://medium.com/@meghakumari2203/engineering-resilient-android-apps-anrs-crashes-the-art-of-not-breaking-production-c3d77ad61ebd
author_url
https://medium.com/@meghakumari2203
status
ok
fetched_at
2026-08-22 12:27:57