Android How to deal with ANRs?
Post
Cancel

How to deal with ANRs?

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.

1
2
3
4
5
6
7
8
9
10
    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

1
2
3
    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:

1
2
3
4
5
    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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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

1
2
3
4
5
6
7
8
9
10
    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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    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! 💻

This post is licensed under CC BY 4.0 by the author.