若主執行緒( Main threat、UI thread ) 處於阻塞的情況,就有可能會觸發 ANR ( Application Not Responding ) 錯誤(如下圖)。這種錯誤是最嚴重的,也會帶來糟糕的使用體驗,當使用者遇到這種情況也只能離開 app。
常見的 ANR 發生原因有:
ANR 最常發生的原因是在主執行緒處理耗時的工作。下方程式碼,我們在主執行緒以 Thread.sleep(2000)
來模擬正在執行一件耗時 2秒的工作。
binding.send.setOnClickListener{
Thread.sleep(2000)
}
從 Log 可以看到有 179個 frames 被跳過了,也就是你的 UI 在這 2 秒的期間是卡住。
Skipped 179 frames! The application may be doing too much work on its main thread.
當執行工作的時間再久一點到 10 秒時,就會出現 ANR 錯誤了。
binding.send.setOnClickListener{
Thread.sleep(10000)
}
ANR in evan.chen.toturial.anrsample (evan.chen.toturial.anrsample/.MainActivity)
為了解決 執行緒間的 race condition , 我們會使用 synchronized 來確保一次只有一個執行緒能存取。在這個例子,雖然已經把耗時的工作放到背景執行緒,但主執行緒在等待的資源被鎖定,仍會造成 ANR,因為這也代表著主執行緒正在等待中。
下方的式程碼說明了:
button1 點擊時,在背景執行緒因為用了 synchronized 鎖定了resource。
button2 點擊時,resource 已被另一個執行緒給鎖定,而主執行緒需要這個被鎖定的 resource 才能完成工作,這也造成了 ANR。
private var resource: String = ""
//button1點擊,在背景執行緒鎖定了 resource
binding.button1.setOnClickListener {
Thread {
synchronized(resource) {
Thread.sleep(10000)
}
}.start()
}
//button2點擊,需等待被另一個執行緒鎖定的 resouce
binding.button2.setOnClickListener {
synchronized(resource) {
println("OK:$resource")
}
}
在 BroadcastReceiver 是不允許執行耗時的工作,取而代之的是收到廣播後使用 WorkManager 來處理耗時工作。
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
//用Thread.sleep來模擬做耗時的事
Thread.sleep(10000)
}
}
這篇我們介紹如何避免 ANR,在 UI 的回應超過 5 秒沒有回應就會發生ANR,但其實使用者與 UI 的互動在0.1~0.2秒就應該有所回應。在後續的 Layout 效能篇,我們會再做深入的介紹。
參考:
https://developer.android.com/topic/performance/vitals/anr