當Android應用程序的UI線程阻塞時間過長時,會觸發(fā)“應用程序不響應”(ANR)錯誤。如果應用程序處于前臺,系統(tǒng)會向用戶顯示一個對話框,如圖1所示。ANR對話框為用戶提供強制退出應用程序的機會。

ANR是一個問題,因為應用程序的主線程(負責更新UI)不能處理用戶輸入的事件或繪制,從而導致用戶感到沮喪。
當下列情況之一發(fā)生時,您的應用將觸發(fā)ANR:
- 當你的活動處于前臺時,你的應用程序
BroadcastReceiver在5秒內(nèi)沒有響應輸入事件或(例如按鍵或屏幕觸摸事件)。 - 雖然在前臺沒有任何活動,但
BroadcastReceiver在相當長的時間內(nèi)還沒有完成執(zhí)行。 - 如果你的應用程序遇到ANR,則可以使用本文中的指導來診斷和解決問題。
檢測和診斷問題
Android提供了多種方式讓您知道你的應用有問題,并幫助你診斷。如果你已經(jīng)發(fā)布了你的應用程序,Android維權人員可以提醒你問題正在發(fā)生,并且有診斷工具可幫助你查找問題。
Android的重要功能
Android的重要功能可以通過Play控制臺提醒你,當你的應用顯示過多的ANR時,可以幫助你提高應用的性能 。Android的重要功能認為應用程序的ANR過度:
- 至少在每日會議的0.47%中至少展示一次ANR。
- 展品2或更多的ANR至少在其日常會議的0.24%。
- 一個日常的會話是指在使用你的應用程序的日子。
診斷ANRs
在診斷ANR時有一些常見的模式可供選擇:
- 該應用程序正在執(zhí)行緩慢的操作涉及主線程上的I / O。
- 該應用程序在主線程上做了長時間的計算。
- 主線程正在對另一個進程執(zhí)行同步聯(lián)編程序調(diào)用,而另一個進程需要很長時間才能返回。
- 主線程被阻塞,等待一個正在另一個線程上發(fā)生的長操作的同步塊。
- 主線程與另一個線程處于死鎖狀態(tài),無論是在你的進程還是通過聯(lián)編程序調(diào)用。主線不僅僅是等待長時間的操作才能完成,而是陷入了僵局。
以下技術可以幫助您找出哪些原因?qū)е履腁NR。
strict模式
使用StrictMode幫助你在開發(fā)應用程序時在主線程上發(fā)現(xiàn)偶然的I / O操作。你可以StrictMode在應用程序或活動級別使用。
啟用背景ANR對話框
Android僅在設備的“ 開發(fā)人員”選項中顯示所有ANR的情況下,才會顯示ANR對話框,用于處理廣播消息所需的時間太長的應用程序。出于這個原因,背景ANR對話并不總是顯示給用戶,但應用程序仍然可能遇到性能問題。
Traceview
您可以使用Traceview在遍歷用例的同時獲取正在運行的應用程序的跟蹤信息,并確定主線程繁忙的地方。
使用一個跟蹤文件
Android /data/anr/traces.txt在設備上的文件中遇到ANR時會存儲一些跟蹤信息 。你可以通過以root用戶身份在設備上啟動shell會話來從仿真程序獲取文件,如以下命令行示例所示:
adb root
adb shell
cat /data/anr/traces.txt
你可以使用設備上的“獲取錯誤報告開發(fā)者”選項或開發(fā)計算機上的“adb bugreport”命令來從物理設備捕獲錯誤報告。
解決問題
在確定問題后,你可以使用本問文中的提示來解決常見問題。
主線程上的代碼變慢
確定你的代碼中應用程序的主線程忙于超過5秒鐘的地方。在你的應用程序中查找可疑用例并嘗試重現(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);
}
在這種情況下,你應該將在主線程中運行的工作移至工作線程。Android框架包括可以幫助將任務移動到工作線程的類,以下代碼顯示了如何使用AsyncTaskhelper類來處理工作線程上的任務:
@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所示。主線程可用于響應用戶事件。

主線程上的IO
在主線程上執(zhí)行IO操作是導致主線程操作速度慢的一個常見原因,這會導致ANR。建議將所有IO操作移至工作線程。
IO操作的一些示例是網(wǎng)絡和存儲操作。
爭用鎖
在某些情況下,導致ANR的工作不會直接在應用程序的主線程上執(zhí)行。如果工作者線程在主線程完成工作所需的資源上持有鎖,則可能會發(fā)生ANR。
例如,圖4顯示了Traceview時間線,其中大部分工作是在工作線程上執(zhí)行的。

但是,如果你的用戶仍然遇到ANR,則應該查看Android設備監(jiān)視器中主線程的狀態(tài)。通常情況下,如果主線程已經(jīng)RUNNABLE準備好更新用戶界面,并且通常是響應的,那么主線程就處 但如果主線程無法恢復執(zhí)行,則處于BLOCKED狀態(tài),無法響應事件。Android設備監(jiān)視器上的狀態(tài)顯示為“ 監(jiān)視”或“ 等待”,如圖5所示。

以下跟蹤顯示了被阻止等待資源的應用程序主線程:
...
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)
...
查看跟蹤可以幫助你找到阻塞主線程的代碼。下面的代碼負責持有阻塞前一個跟蹤中的主線程的鎖:
@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]);
}
}
}
另一個示例是應用程序的主線程正在等待工作線程的結(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ù)庫連接池)或其他互斥(互斥)的機制。
一般來說,你應該評估你的應用程序?qū)Y源所持有的鎖,但是如果你想避免ANR,那么你應該看看為主線程所需的資源所持有的鎖。
確保鎖保持最少的時間,或者甚至更好,評估應用程序是否需要首先舉行。如果使用鎖來確定何時基于工作線程的處理來更新UI,請使用諸如onProgressUpdate()和之類的機制onPostExecute()來在工作線程和主線程之間進行通信。
死鎖
當線程進入等待狀態(tài)時會發(fā)生死鎖,因為所需的資源由另一個線程持有,這也正在等待第一個線程占用的資源。如果應用程序的主線程在這種情況下,ANR可能會發(fā)生。
死鎖在計算機科學中是一個研究得很好的現(xiàn)象,并且有可以用來避免死鎖的死鎖預防算法。
慢速廣播接收機
應用程序可以通過廣播接收器響應廣播消息,例如啟用或禁用飛行模式或更改連接狀態(tài)。當應用程序花費太長時間來處理廣播消息時,會發(fā)生ANR。
ANR發(fā)生在下列情況下:
- 廣播接收機還沒有
onReceive()在相當長的時間內(nèi)完成其方法。 - 廣播接收者呼叫
goAsync()并且不能呼叫finish()該PendingResult對象。 - 你的應用程序只能在
onReceive()方法中執(zhí)行短操作BroadcastReceiver。但是,如果你的應用程序由于廣播消息而需要更復雜的處理,則應將任務推遲到IntentService。
你可以使用Traceview等工具來確定你的廣播接收器是否在應用的主線程上執(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ā)出信號表示它需要更多時間來處理消息。但是,你應該調(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()將不會修復ANR。ANR超時仍然適用。