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īng)用程序的機會沙庐。


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

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. 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)該將在主線程中運行的工作移至工作線程。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)用戶事件忌锯。


圖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ù)庫連接池)或其他互斥(互斥)的機制壮韭。

一般來說北发,你應(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秒的廣播接收器的時間線咙咽。

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

此行為可以通過對該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時間線中的工作線程的工作麸粮。

圖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)系作者
  • 序言:七十年代末视乐,一起剝皮案震驚了整個濱河市洛搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佑淀,老刑警劉巖留美,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伸刃,居然都是意外死亡谎砾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門捧颅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來景图,“玉大人,你說我怎么就攤上這事碉哑≈勘遥” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵扣典,是天一觀的道長妆毕。 經(jīng)常有香客問我,道長贮尖,這世上最難降的妖魔是什么笛粘? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上薪前,老公的妹妹穿的比我還像新娘润努。我一直安慰自己,他們只是感情好序六,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布任连。 她就那樣靜靜地躺著,像睡著了一般例诀。 火紅的嫁衣襯著肌膚如雪澈蟆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天投储,我揣著相機與錄音炸茧,去河邊找鬼。 笑死扔罪,一個胖子當(dāng)著我的面吹牛秉沼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矿酵,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼唬复,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了全肮?” 一聲冷哼從身側(cè)響起敞咧,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辜腺,沒想到半個月后休建,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡评疗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年测砂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片百匆。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡砌些,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出加匈,到底是詐尸還是另有隱情寄症,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布矩动,位于F島的核電站有巧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悲没。R本人自食惡果不足惜篮迎,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一男图、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧甜橱,春花似錦逊笆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至镊掖,卻和暖如春乃戈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亩进。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工症虑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人归薛。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓谍憔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親主籍。 傳聞我的和親對象是個殘疾皇子习贫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容