什么是ANR
ANR:Application Not Responding,即應用程序無響應。
在Android中,ActivityManagerService(簡稱AMS)和WindowManagerService(簡稱WMS)會監測應用程序的響應時間,如果應用程序主線程(即UI線程)在超時時間內對輸入事件沒有處理完畢,或者對特定操作沒有執行完畢,就會出現ANR。
對于輸入事件沒有處理完畢產生的ANR,Android會顯示一個對話框,提示用戶當前應用程序沒有響應,用戶可以選擇繼續等待或者關閉這個應用程序(也就是殺掉這個應用程序的進程)。
為什么會產生ANR
ANR一般有三種類型:
- 輸入事件(按鍵和觸摸事件)5s內沒被處理:Input event dispatching timed out
- BroadcastReceiver的事件(onRecieve方法)在規定時間內沒處理完(前臺廣播為10s,后臺廣播為60s):Timeout of broadcast BroadcastRecord
- Service前臺20s后臺200s未完成啟動:Timeout executing service
- ContentProvider的publish在10s內沒進行完:timeout publishing content providers
ANR產生的常見原因:
- 主線程(UI線程)在做一些阻塞耗時的工作。例如文件讀寫,數據庫讀寫,網絡查詢等。
- 主線程被其他線程鎖。
- cpu被其他進程占用,該進程沒被分配到足夠的cpu資源。
如何分析ANR
從log中找到ANR發生的信息
可以從log中搜索“ANR in”或“am_anr”,會找到ANR發生的log,該行會包含了ANR的時間、進程、是何種ANR等信息。
分析ANR產生的trace文件
ANR產生時,系統會生成一個traces.txt的文件放在/data/anr/下。 可以通過adb命令將其導出到本地:
adb pull data/anr/traces.txt
trace文件記錄了發生ANR前后該進程的各個線程的stack。
分析思路
- 普通阻塞導致的ANR。
- 如果是BroadcastReceiver的ANR可以懷疑BroadCastReceiver.onRecieve()的問題,如果的Service或Provider就懷疑是否其onCreate()的問題。
- 如果某些進程的CPU占用百分比較高,幾乎占用了所有CPU資源,而發生ANR的進程CPU占用為0%或非常低,則認為CPU資源被占用,進程沒有被分配足夠的資源,從而發生了ANR。這種情況多數可以認為是系統狀態的問題,并不是由本應用造成的。
- 如果CPU使用量很少,說明主線程被BLOCK了,可能是主進程被鎖。
- 如果IOwait很高,說明ANR有可能是主線程在進行I/O操作造成的。
- 如果CPU使用量接近100%,說明CPU滿負荷,有可能是CPU饑餓導致了ANR。
- 內存原因。
如何避免ANR
1.合理使用UI主線程,耗時操作放入其他線程工作。
1.1 UI線程盡量只做跟UI相關的工作。
1.2 耗時的工作(比如數據庫操作,I/O,連接網絡或者別的有可能阻礙UI線程的操作)把它放入單獨的線程處理。
2.盡量用Handler來處理UI thread和別的thread之間的交互。
3.合理使用并遵循Android生命周期,避免在onCreate()和onResume()做過多的事情。
4.sharedPreference的使用:
4.1 sharedPreference的commit()方法是同步的,apply()方法一般是異步執行的。在主線程不要用其commit(),用apply()替換。
4.2 sharedPreference的寫是全量寫而非增量寫,所以盡量都修改完同一apply,避免改一點apply一次(apply()方法在Activity stop的時候主線程會等待寫入完成,提交多次就很容易卡)。并且存儲文本也不宜過大,這樣會很慢。另外,如果寫入的是json或者xml,由于需要加和刪轉義符號,速度會比較慢。
5.如果主線程阻塞,開辟單獨的子線程來處理耗時阻塞事務。
6.如果I/O阻塞,一般來說就是文件讀寫或數據庫操作執行在主線程了,可以通過開辟子線程的方式異步執行。
7.如果內存不夠用,增大VM內存,使用largeHeap屬性,排查內存泄露。
拓展
哪些地方是執行在主線程的
各個組件的生命周期函數都不應該有太耗時的操作。
Activity的所有生命周期回調、Service的onCreate()、BroadcastReceiver的onReceive(開個IntentService去執行相應操作)、
ContentProvider的onCreate()是執行在主線程的。
沒有使用子線程的looper的Handler的handleMessage,
post(Runnable)是執行在主線程的。
AsyncTask的回調中除了doInBackground,其他都是執行在主線程的。
View的post(Runnable)是執行在主線程的。
盡量避免主線程的被鎖的情況。
一些同步的操作主線程有可能被鎖,需要等待其他線程釋放相應鎖才能繼續執行,這樣會有一定的ANR風險。對于這種情況有時也可以用異步線程來執行相應的邏輯。另外, 我們要避免死鎖的發生(主線程被死鎖基本就等于要發生ANR了)。