當(dāng)Android應(yīng)用程序的UI線程阻塞時間過長時,會觸發(fā)“應(yīng)用程序不響應(yīng)”(ANR)錯誤河哑。如果應(yīng)用程序處于前臺避诽,系統(tǒng)會向用戶顯示一個對話框,如圖1所示璃谨。ANR對話框為用戶提供強制退出應(yīng)用程序的機會沙庐。
ANR是一個問題,因為應(yīng)用程序的主線程(負(fù)責(zé)更新UI)不能處理用戶輸入的事件或繪制佳吞,從而導(dǎo)致用戶感到沮喪拱雏。
當(dāng)下列情況之一發(fā)生時,您的應(yīng)用將觸發(fā)ANR:
- 當(dāng)你的活動處于前臺時底扳,你的應(yīng)用程序
BroadcastReceiver
在5秒內(nèi)沒有響應(yīng)輸入事件或(例如按鍵或屏幕觸摸事件)铸抑。 - 雖然在前臺沒有任何活動,但
BroadcastReceiver
在相當(dāng)長的時間內(nèi)還沒有完成執(zhí)行衷模。 - 如果你的應(yīng)用程序遇到ANR鹊汛,則可以使用本文中的指導(dǎo)來診斷和解決問題。
檢測和診斷問題
Android提供了多種方式讓您知道你的應(yīng)用有問題算芯,并幫助你診斷柒昏。如果你已經(jīng)發(fā)布了你的應(yīng)用程序,Android維權(quán)人員可以提醒你問題正在發(fā)生熙揍,并且有診斷工具可幫助你查找問題职祷。
Android的重要功能
Android的重要功能可以通過Play控制臺提醒你,當(dāng)你的應(yīng)用顯示過多的ANR時届囚,可以幫助你提高應(yīng)用的性能 有梆。Android的重要功能認(rèn)為應(yīng)用程序的ANR過度:
- 至少在每日會議的0.47%中至少展示一次ANR。
- 展品2或更多的ANR至少在其日常會議的0.24%意系。
- 一個日常的會話是指在使用你的應(yīng)用程序的日子泥耀。
診斷ANRs
在診斷ANR時有一些常見的模式可供選擇:
- 該應(yīng)用程序正在執(zhí)行緩慢的操作涉及主線程上的I / O。
- 該應(yīng)用程序在主線程上做了長時間的計算蛔添。
- 主線程正在對另一個進(jìn)程執(zhí)行同步聯(lián)編程序調(diào)用痰催,而另一個進(jìn)程需要很長時間才能返回。
- 主線程被阻塞迎瞧,等待一個正在另一個線程上發(fā)生的長操作的同步塊夸溶。
- 主線程與另一個線程處于死鎖狀態(tài),無論是在你的進(jìn)程還是通過聯(lián)編程序調(diào)用凶硅。主線不僅僅是等待長時間的操作才能完成缝裁,而是陷入了僵局。
以下技術(shù)可以幫助您找出哪些原因?qū)е履腁NR足绅。
strict模式
使用StrictMode幫助你在開發(fā)應(yīng)用程序時在主線程上發(fā)現(xiàn)偶然的I / O操作捷绑。你可以StrictMode在應(yīng)用程序或活動級別使用韩脑。
啟用背景ANR對話框
Android僅在設(shè)備的“ 開發(fā)人員”選項中顯示所有ANR的情況下,才會顯示ANR對話框粹污,用于處理廣播消息所需的時間太長的應(yīng)用程序段多。出于這個原因,背景ANR對話并不總是顯示給用戶壮吩,但應(yīng)用程序仍然可能遇到性能問題衩匣。
Traceview
您可以使用Traceview在遍歷用例的同時獲取正在運行的應(yīng)用程序的跟蹤信息,并確定主線程繁忙的地方粥航。
使用一個跟蹤文件
Android /data/anr/traces.txt
在設(shè)備上的文件中遇到ANR
時會存儲一些跟蹤信息 。你可以通過以root用戶身份在設(shè)備上啟動shell會話來從仿真程序獲取文件生百,如以下命令行示例所示:
adb root
adb shell
cat /data/anr/traces.txt
你可以使用設(shè)備上的“獲取錯誤報告開發(fā)者”選項或開發(fā)計算機上的“adb bugreport”
命令來從物理設(shè)備捕獲錯誤報告递雀。
解決問題
在確定問題后,你可以使用本問文中的提示來解決常見問題蚀浆。
主線程上的代碼變慢
確定你的代碼中應(yīng)用程序的主線程忙于超過5秒鐘的地方缀程。在你的應(yīng)用程序中查找可疑用例并嘗試重現(xiàn)ANR。
例如市俊,圖2顯示了Traceview時間線杨凑,其中主線程忙于超過5秒鐘。
圖2顯示了大多數(shù)有問題的代碼發(fā)生在onClick(View)處理程序中摆昧,如以下代碼示例所示:
@Override
public void onClick(View view) {
// This task runs on the main thread.
BubbleSort.sort(data);
}
在這種情況下撩满,你應(yīng)該將在主線程中運行的工作移至工作線程。Android框架包括可以幫助將任務(wù)移動到工作線程的類绅你,以下代碼顯示了如何使用AsyncTaskhelper類
來處理工作線程上的任務(wù):
@Override
public void onClick(View view) {
// The long-running operation is run on a worker thread
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
BubbleSort.sort(params[0]);
}
}.execute(data);
}
Traceview顯示大部分代碼在工作線程上運行伺帘,如圖3所示。主線程可用于響應(yīng)用戶事件忌锯。
主線程上的IO
在主線程上執(zhí)行IO操作是導(dǎo)致主線程操作速度慢的一個常見原因伪嫁,這會導(dǎo)致ANR。建議將所有IO操作移至工作線程偶垮。
IO操作的一些示例是網(wǎng)絡(luò)和存儲操作张咳。
爭用鎖
在某些情況下,導(dǎo)致ANR的工作不會直接在應(yīng)用程序的主線程上執(zhí)行似舵。如果工作者線程在主線程完成工作所需的資源上持有鎖脚猾,則可能會發(fā)生ANR。
例如啄枕,圖4顯示了Traceview時間線婚陪,其中大部分工作是在工作線程上執(zhí)行的。
但是频祝,如果你的用戶仍然遇到ANR泌参,則應(yīng)該查看Android設(shè)備監(jiān)視器中主線程的狀態(tài)脆淹。通常情況下,如果主線程已經(jīng)RUNNABLE準(zhǔn)備好更新用戶界面沽一,并且通常是響應(yīng)的盖溺,那么主線程就處 但如果主線程無法恢復(fù)執(zhí)行,則處于BLOCKED狀態(tài)铣缠,無法響應(yīng)事件烘嘱。Android設(shè)備監(jiān)視器上的狀態(tài)顯示為“ 監(jiān)視”或“ 等待”,如圖5所示蝗蛙。
以下跟蹤顯示了被阻止等待資源的應(yīng)用程序主線程:
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
查看跟蹤可以幫助你找到阻塞主線程的代碼蝇庭。下面的代碼負(fù)責(zé)持有阻塞前一個跟蹤中的主線程的鎖:
@Override
public void onClick(View v) {
// The worker thread holds a lock on lockedResource
new LockTask().execute(data);
synchronized (lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0]);
}
}
}
另一個示例是應(yīng)用程序的主線程正在等待工作線程的結(jié)果,如以下代碼所示:
de
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// Wait for this worker thread’s notification
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// Finished, notify the main thread
notify();
}
}
}
還有一些其他的情況下捡硅,可以阻止主線程哮内,包括使用線程Lock,Semaphore以及資源池(如數(shù)據(jù)庫連接池)或其他互斥(互斥)的機制壮韭。
一般來說北发,你應(yīng)該評估你的應(yīng)用程序?qū)Y源所持有的鎖,但是如果你想避免ANR喷屋,那么你應(yīng)該看看為主線程所需的資源所持有的鎖琳拨。
確保鎖保持最少的時間,或者甚至更好屯曹,評估應(yīng)用程序是否需要首先舉行狱庇。如果使用鎖來確定何時基于工作線程的處理來更新UI,請使用諸如onProgressUpdate()和之類的機制onPostExecute()來在工作線程和主線程之間進(jìn)行通信恶耽。
死鎖
當(dāng)線程進(jìn)入等待狀態(tài)時會發(fā)生死鎖僵井,因為所需的資源由另一個線程持有,這也正在等待第一個線程占用的資源驳棱。如果應(yīng)用程序的主線程在這種情況下批什,ANR可能會發(fā)生。
死鎖在計算機科學(xué)中是一個研究得很好的現(xiàn)象社搅,并且有可以用來避免死鎖的死鎖預(yù)防算法驻债。
慢速廣播接收機
應(yīng)用程序可以通過廣播接收器響應(yīng)廣播消息,例如啟用或禁用飛行模式或更改連接狀態(tài)形葬。當(dāng)應(yīng)用程序花費太長時間來處理廣播消息時合呐,會發(fā)生ANR。
ANR發(fā)生在下列情況下:
- 廣播接收機還沒有
onReceive()
在相當(dāng)長的時間內(nèi)完成其方法笙以。 - 廣播接收者呼叫
goAsync()
并且不能呼叫finish()
該PendingResult
對象淌实。 - 你的應(yīng)用程序只能在
onReceive()
方法中執(zhí)行短操作BroadcastReceiver
。但是,如果你的應(yīng)用程序由于廣播消息而需要更復(fù)雜的處理拆祈,則應(yīng)將任務(wù)推遲到IntentService
恨闪。
你可以使用Traceview
等工具來確定你的廣播接收器是否在應(yīng)用的主線程上執(zhí)行長時間運行的操作。例如放坏,圖6顯示了在主線程上處理消息大約100秒的廣播接收器的時間線咙咽。
此行為可以通過對該onReceive()
方法執(zhí)行長時間運行的操作來引起BroadcastReceiver
,如以下示例所示:
@Override
public void onReceive(Context context, Intent intent) {
// This is a long-running operation
BubbleSort.sort(data);
}
在這樣的情況下淤年,建議將長時間運行的操作移動到一個IntentService
,因為它使用一個工作線程執(zhí)行工作钧敞。以下代碼顯示如何使用一個IntentService
來處理長時間運行的操作:
@Override
public void onReceive(Context context, Intent intent) {
// The task now runs on a worker thread.
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
IntentService
在工作者線程而不是主線程上執(zhí)行長時間運行的操作。圖7顯示了推遲到Traceview
時間線中的工作線程的工作麸粮。
你的廣播接收器可以用goAsync()
來向系統(tǒng)發(fā)出信號表示它需要更多時間來處理消息溉苛。但是,你應(yīng)該調(diào)用finish()
的PendingResult對象弄诲。以下示例顯示如何調(diào)用finish()
從而讓系統(tǒng)回收廣播接收器并避免ANR
:
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// This is a long-running operation
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
但是炊昆,如果廣播在后臺,將代碼從慢速廣播接收器移動到另一個線程并使用goAsync()
將不會修復(fù)ANR
威根。ANR超時仍然適用。