Android開發(fā)套路# ANR(應(yīng)用程序不響應(yīng))

當(dāng)Android應(yīng)用程序的UI線程阻塞時間過長時,會觸發(fā)“應(yīng)用程序不響應(yīng)”(ANR)錯誤。如果應(yīng)用程序處于前臺,系統(tǒng)會向用戶顯示一個對話框,如圖1所示。ANR對話框?yàn)橛脩籼峁?qiáng)制退出應(yīng)用程序的機(jī)會。


圖1.顯示給用戶的ANR對話框.png

ANR是一個問題,因?yàn)閼?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ā)人員”選項(xiàng)中顯示所有ANR的情況下,才會顯示ANR對話框,用于處理廣播消息所需的時間太長的應(yīng)用程序。出于這個原因,背景ANR對話并不總是顯示給用戶,但應(yīng)用程序仍然可能遇到性能問題。

Traceview

您可以使用Traceview在遍歷用例的同時獲取正在運(yùn)行的應(yīng)用程序的跟蹤信息,并確定主線程繁忙的地方。

使用一個跟蹤文件

Android /data/anr/traces.txt在設(shè)備上的文件中遇到ANR時會存儲一些跟蹤信息 。你可以通過以root用戶身份在設(shè)備上啟動shell會話來從仿真程序獲取文件,如以下命令行示例所示:

adb root
adb shell
cat /data/anr/traces.txt

你可以使用設(shè)備上的“獲取錯誤報告開發(fā)者”選項(xiàng)或開發(fā)計算機(jī)上的“adb bugreport”命令來從物理設(shè)備捕獲錯誤報告。

解決問題

在確定問題后,你可以使用本問文中的提示來解決常見問題。

主線程上的代碼變慢

確定你的代碼中應(yīng)用程序的主線程忙于超過5秒鐘的地方。在你的應(yīng)用程序中查找可疑用例并嘗試重現(xiàn)ANR。

例如,圖2顯示了Traceview時間線,其中主線程忙于超過5秒鐘。


圖2. Traceview時間線顯示繁忙的主線程.png

圖2顯示了大多數(shù)有問題的代碼發(fā)生在onClick(View)處理程序中,如以下代碼示例所示:

@Override
public void onClick(View view) {
   // This task runs on the main thread.
   BubbleSort.sort(data);
}

在這種情況下,你應(yīng)該將在主線程中運(yùn)行的工作移至工作線程。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顯示大部分代碼在工作線程上運(yùn)行,如圖3所示。主線程可用于響應(yīng)用戶事件。


圖3. Traceview時間線顯示工作線程處理的工作.png

主線程上的IO

在主線程上執(zhí)行IO操作是導(dǎo)致主線程操作速度慢的一個常見原因,這會導(dǎo)致ANR。建議將所有IO操作移至工作線程。
IO操作的一些示例是網(wǎng)絡(luò)和存儲操作。

爭用鎖

在某些情況下,導(dǎo)致ANR的工作不會直接在應(yīng)用程序的主線程上執(zhí)行。如果工作者線程在主線程完成工作所需的資源上持有鎖,則可能會發(fā)生ANR。

例如,圖4顯示了Traceview時間線,其中大部分工作是在工作線程上執(zhí)行的。


圖4.顯示正在工作線程上執(zhí)行的工作的Traceview時間線.png

但是,如果你的用戶仍然遇到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所示。


圖5.監(jiān)視器狀態(tài)中的主線程.png

以下跟蹤顯示了被阻止等待資源的應(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ù)庫連接池)或其他互斥(互斥)的機(jī)制。

一般來說,你應(yīng)該評估你的應(yīng)用程序?qū)Y源所持有的鎖,但是如果你想避免ANR,那么你應(yīng)該看看為主線程所需的資源所持有的鎖。

確保鎖保持最少的時間,或者甚至更好,評估應(yīng)用程序是否需要首先舉行。如果使用鎖來確定何時基于工作線程的處理來更新UI,請使用諸如onProgressUpdate()和之類的機(jī)制onPostExecute()來在工作線程和主線程之間進(jìn)行通信。

死鎖

當(dāng)線程進(jìn)入等待狀態(tài)時會發(fā)生死鎖,因?yàn)樗璧馁Y源由另一個線程持有,這也正在等待第一個線程占用的資源。如果應(yīng)用程序的主線程在這種情況下,ANR可能會發(fā)生。

死鎖在計算機(jī)科學(xué)中是一個研究得很好的現(xiàn)象,并且有可以用來避免死鎖的死鎖預(yù)防算法。

慢速廣播接收機(jī)

應(yīng)用程序可以通過廣播接收器響應(yīng)廣播消息,例如啟用或禁用飛行模式或更改連接狀態(tài)。當(dāng)應(yīng)用程序花費(fèi)太長時間來處理廣播消息時,會發(fā)生ANR。

ANR發(fā)生在下列情況下:

  • 廣播接收機(jī)還沒有onReceive()在相當(dāng)長的時間內(nèi)完成其方法。
  • 廣播接收者呼叫goAsync()并且不能呼叫finish()PendingResult對象。
  • 你的應(yīng)用程序只能在onReceive()方法中執(zhí)行短操作BroadcastReceiver。但是,如果你的應(yīng)用程序由于廣播消息而需要更復(fù)雜的處理,則應(yīng)將任務(wù)推遲到IntentService

你可以使用Traceview等工具來確定你的廣播接收器是否在應(yīng)用的主線程上執(zhí)行長時間運(yùn)行的操作。例如,圖6顯示了在主線程上處理消息大約100秒的廣播接收器的時間線。

圖6.顯示```BroadcastReceiver```在主線程上工作的```Traceview```時間線.png

此行為可以通過對該onReceive()方法執(zhí)行長時間運(yùn)行的操作來引起BroadcastReceiver,如以下示例所示:

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

在這樣的情況下,建議將長時間運(yùn)行的操作移動到一個IntentService,因?yàn)樗褂靡粋€工作線程執(zhí)行工作。以下代碼顯示如何使用一個IntentService來處理長時間運(yùn)行的操作:

@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í)行長時間運(yùn)行的操作。圖7顯示了推遲到Traceview時間線中的工作線程的工作。

圖7. Traceview時間線顯示在工作線程上處理的廣播消息.png

你的廣播接收器可以用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超時仍然適用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容