How to deal with ANRs?

Photo by Pathum Danthanarayana on Unsplash

1. What is ANR?

ANR stands for Application Not Responding, which is the state that your application cannot process user input events or even draw.

The root cause of ANR is when the application’s UI thread has been blocked for too long:

2. Some common cases that cause ANR

  • Perform long-running tasks (IO, heavy computation,…) on main thread
  • Main thread is blocked by a long execution task in another thread
  • Main thread is doing asynchronous binder call with another process, which take a long time to return the result
  • Too slow broadcast receiver

3. How to detect and diagnose ANRs?

Strict Mode

Enable Strict Mode allows you to detect accident network/IO calls on main thread.

if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectNetwork()
.detectDiskReads()
.detectDiskWrites()
.penaltyLog()
.build()
)
}

ANR dialog

Enable Show all ANRs dialog from Developer Options to visually alerting you whenever has ANR even for background apps.

Android Studio Profiler

The Profiler is available on Android Studio 3.0 and above.

Using the Profiler, you can observe the threads along the app’s timeline. Base on that you can analyze and identify ANRs.

Bug Report

Another way to diagnose ANR is to use the bug report feature.

To generate a bug report, you can either:

  • Select from the developer options
  • Or, use the following commands:
adb bugreport target-location

4. How to fix ANRs?

Long-running task on the main thread

btn_execute.setOnClickListener {
LongRunningTask().execute()
}

You can easily detect those long-running task by observing the timeline of main thread in the Profiler:

To resolve this, we simply just move the long-running task to another thread:

btn_execute.setOnClickListener {
Thread {
LongRunningTask().execute()
}.start()
}

And the main thread won’t be blocked anymore 🎉

Lock contention

In some cases, even your long-running task executes off the main thread, your app still may face ANRs due to lock contention.

btn_execute.setOnClickListener {
Thread {
LongRunningTask().execute()
}.start()

synchronized(lockObject) {
tv_status.text = "Finished Execution"
}
}
inner class LongRunningTask {
fun execute(): String {
synchronized(lockObject) {
var result = ""
for (i in 0..Int.MAX_VALUE) {
result = "$i"
}
return result
}
}
}

Those lock contention can be detected by analyzing the trace/bug report file:

------ VM TRACES AT LAST ANR (/data/anr/anr_2020-02-24-11-39-05-971: 2020-02-24 11:39:08) ------"main" prio=5 tid=1 Blocked
| group="main" sCount=2 dsCount=0 flags=1 obj=0x714c91f0 self=0xee537800
| sysTid=32337 nice=-10 cgrp=default sched=0/0 handle=0xeebb8dc8
| state=S schedstat=( 1084437004 741209249 935 ) utm=38 stm=69 core=0 HZ=100
| stack=0xff086000-0xff088000 stackSize=8192KB
| held mutexes=
at com.icedtealabs.anrdemo.MainActivity$onCreate$1.onClick(MainActivity.kt:20)
- waiting to lock <0x0a1b1d2e> (a java.lang.Object) held by thread 22
......"Thread-8" prio=5 tid=22 Native
| group="main" sCount=2 dsCount=0 flags=1 obj=0x12d80000 self=0xe34f3c00
| sysTid=32553 nice=0 cgrp=default sched=0/0 handle=0xbfaab230
| state=S schedstat=( 211312896032 78218568213 176791 ) utm=3214 stm=17916 core=1 HZ=100
| stack=0xbf9a8000-0xbf9aa000 stackSize=1040KB
| held mutexes=
at java.lang.Integer.toString(Integer.java:437)
at java.lang.String.valueOf(String.java:3032)
at com.icedtealabs.anrdemo.MainActivity$LongRunningTask.execute(MainActivity.kt:32)
- locked <0x0a1b1d2e> (a java.lang.Object)
at com.icedtealabs.anrdemo.MainActivity$onCreate$1$1.run(MainActivity.kt:17)
at java.lang.Thread.run(Thread.java:919)

By reading the bug report, you can easily specify the lock detention root cause and resolve it! 🎉

Slow BroadcastReceiver

private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
LongRunningTask().execute()
}
}
btn_execute.setOnClickListener {
broadcastManager.sendBroadcast(Intent("custom-action"))
tv_status.text = "Finish Execution"
}

In order to find the slow BroadcastReceiver, you can analyze the trace/bug report:

------ VM TRACES AT LAST ANR (/data/anr/anr_2020-02-24-12-02-02-111: 2020-02-24 12:02:03) ------DALVIK THREADS (13):
"main" prio=5 tid=1 Runnable
| group="main" sCount=0 dsCount=0 flags=0 obj=0x714c91f0 self=0xee537800
| sysTid=17793 nice=-10 cgrp=default sched=0/0 handle=0xeebb8dc8
| state=R schedstat=( 11977233726 2817242624 14105 ) utm=871 stm=325 core=1 HZ=100
| stack=0xff086000-0xff088000 stackSize=8192KB
| held mutexes= "mutator lock"(shared held)
at java.lang.Integer.stringSize(Integer.java:507)
at java.lang.Integer.toString(Integer.java:431)
at java.lang.String.valueOf(String.java:3032)
at com.icedtealabs.anrdemo.MainActivity$LongRunningTask.execute(MainActivity.kt:39)
at com.icedtealabs.anrdemo.MainActivity$broadcastReceiver$1.onReceive(MainActivity.kt:18)
at androidx.localbroadcastmanager.content.LocalBroadcastManager.execute
PendingBroadcasts(LocalBroadcastManager.java:313)
at androidx.localbroadcastmanager.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:121)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)

To resolve this, you can simply move all the heavy execution code in the BroadcastReceiver to an IntentService:

private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
startService(Intent(context, MyIntentService::class.java))
}
}

btn_execute.setOnClickListener {
broadcastManager.sendBroadcast(Intent("custom-action"))
tv_status.text = "Finish Execution"
}

class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
LongRunningTask().execute()
}
}

If you have any feedback/corrections, please feel free to leave a comment below.

Thanks for reading and happy coding! 💻

Original post on Iced Tea Labs.

📝 Save this story in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

--

Glasses 👓 Geek 💻 Backpacker 🎒 Climber 🧗‍♂️ http://icedtealabs.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Kafka connect overview and build a data pipeline using Mysql and Kafka Connect

Push Notification Basics (2 of 2)

Script Communication in Unity Using GetComponent and TryGetComponent

The What, Why and Wow behind the WSO2 Micro Integrator Containers

How to convert a Legacy PPT Presentation to PPTX in Python

Why should you choose to move to Flutter in 2019?

Build Website in Synology NAS, Using DDNS and Port Forwarding

Fix windows 10 inaccessible boot device BSOD Error

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Trinh Le

Trinh Le

Glasses 👓 Geek 💻 Backpacker 🎒 Climber 🧗‍♂️ http://icedtealabs.com

More from Medium

Should I root My Phone in 2022

Quick hacks to enhance your Android gaming experience(2022)

Common errors found when generating Android certificates (and how to fix them!)

Finding courage and inspiration in the developer community