一、首先,了解一下什么是ANR
ANR,是“Application Not Responding”的縮寫,即“應用程序無響應”。系統會向用戶顯示一個對話框,用戶可以選擇“等待”而讓程序繼續運行,也可以選擇“強制關閉”。
在Android中,應用程序的響應是由Activity Manager和WindowManager系統服務監視的 。當它監測到A、B、C情況中的一個時,Android就會針對特定的應用程序顯示ANR:
-
A.在5秒內沒有響應輸入的事件(例如,按鍵按下,屏幕觸摸)--主要類型
-
B.BroadcastReceiver在10秒內沒有執行完畢
-
C.Service在特定時間內(20秒內)無法處理完成--小概率類型
造成ABC的原因有很多,比如在主線程中做了非常耗時的操作,如下載,io異常等。還需要注意的是產生這種ANR的前提是要有輸入事件,如果用戶沒有觸發任何輸入事件,即便是主線程阻塞了,也不會產生ANR,因為InputDispatcher沒有分發事件給應用程序,當然也不會檢測處理超時和報告ANR了。
各個應用進程和系統進程的函數堆棧信息都輸出到了/data/anr/traces.txt的文件中
二、ANR的一般分析思路
- 從手機的/data/traces/目錄導出traces.txt文件;
- 從traces.txt文件獲取ANR產生的時間點T1;
- 從traces.txt文件中獲取main線程的運行狀態、調用棧;
- 查看logcat日志,搜索“ANR in”,找出ANR產生的時間點T2,以及時間點T2前后幾秒鍾,在系統中運行的各進程CPU耗時占比;
- 在步驟4中找出目標進程的cpu耗時占比,確認是user占比高,還是kernel占比高,還是iowait占比高;
- 根據時間點T1、時間點T2,校准ANR產生的時間范圍 [T1, T2] 或 [T2, T1];
- 查看logcat日志,將日志定位到步驟2)中獲取的時間點 [T1, T2] 或 [T2, T1]附近;
- 查看logcat日志在時間點 [T1, T2] 或 [T2, T1]附近前后2分鍾的日志,以還原ANR產生前后的場景;
- 根據以上步驟得出的信息,定位代碼中可能的問題代碼塊;
- 解決問題,或提出階段分析結論。
三、測試人員碰到ANR問題,應該怎么做
-
首先,應該截圖,保留“證據”;
-
描述清楚出現步驟,並嘗試復現,確認是否必現或出現概率
-
導出traces文件
獲取traces文件命令:
adb pull /data/anr
- 導出logcat日志信息。
擴展一:traces文件信息解析
開頭顯示進程號、ANR發生的時間點和進程名稱
----- pid 9183 at 2012-09-28 22:20:42 ----
Cmd line: com.example.anrdemo
DALVIK THREADS: //以下是各個線程的函數堆棧信息
//依次是:線程名、線程優先級、線程號、線程當前狀態(TIMED_WAIT或SUSPENDED在anr時很常見)
//線程名稱后面標識有daemon,說明這是個守護線程
"main" prio=5 tid=1 TIMED_WAIT
//依次是:線程組名稱、suspendCount個數、debugSuspendCount個數、線程的Java對象地址、線程的Native對象地址
| group="main" sCount=1 dsCount=0 obj=0x4025b1b8 self=0xce68
//sysTid是線程號,主線程的線程號和進程號相同
| sysTid=9183 nice=0 sched=0/0 cgrp=default
handle=-1345002368| schedstat=( 140838632 210998525 213 )
at java.lang.VMThread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1213)
.......
//Binder線程是進程的線程池中用來處理binder請求的線程
"Binder Thread #2" prio=5 tid=8 NATIVE
| group="main" sCount=1 dsCount=0 obj=0x40750b90 self=0x1440b8
| sysTid=9190 nice=0 sched=0/0 cgrp=default handle=1476256
| schedstat=( 915528 18463135 4 )
at dalvik.system.NativeStart.run(Native Method)
//JDWP線程是支持虛擬機調試的線程,不需要關心
"JDWP" daemon prio=5 tid=5 VMWAIT
| group="system" sCount=1 dsCount=0 obj=0x4074b878 self=0x16c958
| sysTid=9187 nice=0 sched=0/0 cgrp=default handle=1510224
| schedstat=( 366211 2807617 7 )
t dalvik.system.NativeStart.run(Native Method)
//“Signal Catcher”負責接收和處理kernel發送的各種信號,例如SIGNAL_QUIT、SIGNAL_USR1等就是被該線程
//接收到,這個文件的內容就是由該線程負責輸出的,可以看到它的狀態是RUNNABLE,不過此線程也不需要關心
"Signal Catcher" daemon prio=5 tid=4 RUNNABLE
| group="system" sCount=0 dsCount=0 obj=0x4074b7b8 self=0x150008
| sysTid=9186 nice=0 sched=0/0 cgrp=default handle=1501664
| schedstat=( 1708985 6286621 9 )
擴展二:如何避免ANR
-
絕對不要在主線程上進行復雜耗時的操作,比如說發送接收網絡數據、進行大量計算、操作數據庫、讀寫文件等,統統采用異步操作
-
broadCastReceiver 要進行復雜操作的的時候,可以在onReceive()方法中啟動一個IntentService或者JobIntentService去做。
-
Service中的耗時操作最好也是采用異步任務
-
在設計及代碼編寫階段避免出現出現同步/死鎖、死循環等不恰當情況