Android 系統 ANR 分析詳解

  • 什麼是 ANR
  • ANR 產生的原因
  • ANR 出現流程分析
  • 發生 ANR 如何定位
  • 如何避免和解決 ANR

附 Google 官網說明鏈接:Keeping your app responsive

1. 什麼是 ANR

  • ANR:Application Not Responding,即應用無響應
  • 爲用戶在主線程長時間被阻塞
  • Android 系統自身提供的一種檢測機制
-> ANR 的類型

ANR 一般有三種類型:

  1. KeyDispatchTimeout(5秒超時) – 主要類型按鍵或觸摸事件在特定時間內無響應
  2. BroadCastTimeout(10秒超時) – 一個廣播接收者流程沒有在 10 秒之內完成
自定義修改按鍵響應超時時間

在 frameworks/base/ 下的 ActivityManagerService.java 中定義了具體的超時時間,默認爲 5 秒

// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

2 ANR 產生的原因

超時時間的計數一般是從按鍵分發給 APP 開始。超時的原因一般有兩種:

  1. 當前的事件沒有機會得到處理(即 UI 線程正在處理前一個事件,沒有及時的完成或者 looper 因爲某種原因阻塞住了)
  2. 當前的事件正在處理,但是沒有及時完成

需要滿足的條件:

  1. 主線程:只有應用的主線程響應超時纔會導致 ANR
  2. 超時時間(Timeout):導致 ANR 的原因不同,系統限定的時間也不同,但只要超過時間上限,系統就會發出 Signal 3,產生 ANR
  3. 輸入操作/特定操作:輸入操作指的是按鍵、觸屏等操作,特定操作指的是廣播、服務中執行耗時操作等

下面總結了幾點常見的引起 ANR 的情況:

1. 應用程序自身引起的
   例如,主線程阻塞(block)、掛起、死鎖、死循環、執行耗時操作等
2. 其他應用進程引起的
   例如,其他進程的 CPU 佔用率過高、某一時刻系統 CPU 負載過高等,
   都會導致當前進程無法搶到 CPU 時間片
3. IO wait
   文件讀寫耗時較久,佔用 CPU 時間片較長,導致 ANR

小技巧:如果你在解決其他問題時也需要查看 Java 進程中各個線程的函數堆棧信息,就可以使用向目標進程發送 SIGNAL_QUIT(3),它並不會讓進程真正退出。

3. ANR 出現流程分析

1. 輸入時間響應超時導致 ANR 流程

在系統輸入管理服務進程(InputManagerService)中有一個線程(InputDispathcerThread)專門管理輸入事件的分發,在該線程處理輸入事件的過程中,回調 InputDispatcher 對象方法不斷的檢測處理過程是否超時,一旦超時,則會回調調用 InputMethod 對象的 notifyANR 方法,其會最終出發 AMS 中 handler 對象的 SHOW_NOT_RESPONDING_MSG 這個事件,顯示 ANR 對話框。

2. 廣播過程發生 ANR 流程

廣播分爲三類:普通、有序、異步。只有有序(ordered)的廣播纔會發生超時,而在 AndroidManifest 中註冊的廣播都會被當做有序廣播來處理,會被放在廣播的隊列中串行處理。AMS 在處理廣播隊列時,會設置一個超時時間,當處理一個廣播達到超時時間的限制時,就會觸發 BroadcastQueue 類對象的 processNextBroadcast 方法來判斷是否超時,如果超時,就會終止該廣播,觸發 ANR 對話框。

3. UI 線程

4. 發生 ANR 如何定位

系統發生 ANR 的時候,一般會以以下三種方式記錄

  • logcat 日誌
  • /data/anr/traces.txt
  • dropBox 服務

4.1 通過 logcat 文件分析

-> 舉例說明:

04-01 13:12:11.572 I/InputDispatcher( 220): Application is not responding:Window{2b263310com.android.email/com.android.email.activity.SplitScreenActivitypaused=false}.
5009.8ms since event, 5009.5ms since waitstarted
04-0113:12:11.572 I/WindowManager( 220): Input event 
dispatching timedout sending 
tocom.android.email/com.android.email.activity.SplitScreenActivity

// 1. 發生 ANR 的時間和生成 trace.txt 的時間
04-01 13:12:14.123 I/Process( 220): Sending signal. PID: 21404 SIG: 
04-01 13:12:14.123 I/dalvikvm(21404):threadid=4: reacting to 
signal 3 
// 2. ANR 發生的地點
04-0113:12:15.872 E/ActivityManager( 220): ANR in 
com.android.email(com.android.email/.activity.SplitScreenActivity)
04-0113:12:15.872 E/ActivityManager( 220): 
// 3. ANR 的類型
Reason:keyDispatchingTimedOut
// 4. ANR 發生前 CPU 的負載情況
04-0113:12:15.872 E/ActivityManager( 220): Load: 8.68 / 8.37 / 8.53
04-0113:12:15.872 E/ActivityManager( 220): CPUusage from 4361ms to 699ms ago 
04-0113:12:15.872 E/ActivityManager( 220): 5.5%21404/com.android.email: 1.3% user + 4.1% kernel / faults:
10 minor
04-0113:12:15.872 E/ActivityManager( 220): 4.3%220/system_server: 2.7% user + 1.5% kernel / faults: 11
minor 2 major
04-0113:12:15.872 E/ActivityManager( 220): 0.9%52/spi_qsd.0: 0% user + 0.9% kernel
04-0113:12:15.872 E/ActivityManager( 220): 0.5%65/irq/170-cyttsp-: 0% user + 0.5% kernel
04-0113:12:15.872 E/ActivityManager( 220): 0.5%296/com.android.systemui: 0.5% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 100%TOTAL: 4.8% user + 7.6% kernel + 87% iowait
// 5. ANR 發生後 CPU 的負載情況
04-0113:12:15.872 E/ActivityManager( 220): CPUusage from 3697ms to 4223ms 
04-0113:12:15.872 E/ActivityManager( 220): 25%21404/com.android.email: 25% user + 0% kernel / faults: 191 minor
04-0113:12:15.872 E/ActivityManager( 220): 16% 21603/__eas(par.hakan: 16% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 7.2% 21406/GC: 7.2% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 1.8% 21409/Compiler: 1.8% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 5.5%220/system_server: 0% user + 5.5% kernel / faults: 1 minor
04-0113:12:15.872 E/ActivityManager( 220): 5.5% 263/InputDispatcher: 0% user + 5.5% kernel
04-0113:12:15.872 E/ActivityManager( 220): 32%TOTAL: 28% user + 3.7% kernel

從 LOG 上可以提取到的有效信息:

1. ANR 發生的時間、地點以及類型

類型爲 keyDispatchingTimedOut,即按鍵事件輸入響應超時,發生在 com.android.email 應用中

2. CPU 負載情況
  • 如果 CPU 佔用率接近 100%,說明當前設備很忙,有可能是 CPU 飢餓導致了ANR
  • 如果 IOWait 很高,說明主線程正在進行 IO 操作
  • 如果 CPU 佔用率很低,則說明當前主線程被 BLOCK 了

從上面的 LOG 可以看出,total CPU 佔用率達到 100%,並且 io wait 佔用 87%,說明當前主線程正在進行 io 操作。

4.2 通過 traces.xml 文件分析

trace 文件中會標明 Thread 的狀態,如:

DALVIK THREADS (12):
// 當前線程名 - "main"
// 線程優先級 - "prio=5"
// 線程id - "tid=1"
// 線程狀態 - "Sleeping"
"main" prio=5 tid=1 Runnable

其他常用的狀態定義在 Thread.java 文件中:

ThreadState (defined at "dalvik/vm/thread.h")
THREAD_UNDEFINED = -1, / makes enum compatible with int32_t /
THREAD_ZOMBIE = 0, / 線程死亡,終止運行 /
THREAD_RUNNING = 1, / 線程可運行或正在運行 /
THREAD_TIMED_WAIT = 2, / 執行了帶有超時參數的 wait、sleep 或 join 函數 /
THREAD_MONITOR = 3, / 線程阻塞 /
THREAD_WAIT = 4, / 執行了無超時參數的 wait 函數 /
THREAD_INITIALIZING= 5, / 新建,正在初始化,爲其分配資源 /
THREAD_STARTING = 6, / 新建,正在啓動 /
THREAD_NATIVE = 7, / 正在執行 JNI 本地函數 /
THREAD_VMWAIT = 8, / 正在等待 VM 資源 /
THREAD_SUSPENDED = 9, / suspended, usually by GC or debugger /

舉例分析:

----- pid 609 at 2019-02-16 05:00:10 -----
Cmd line: com.android.systemui
...
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x748ef1f0 self=0xb0851000
  | sysTid=609 nice=0 cgrp=default sched=0/0 handle=0xb4a3f494
  | state=S schedstat=( 392030973719 182772499142 1025298 ) utm=33261 stm=5941 core=2 HZ=100
  | stack=0xbe5fa000-0xbe5fc000 stackSize=8MB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/609/stack)
  native: #00 pc 00019d74  /system/lib/libc.so (syscall+28)
  native: #01 pc 0001d11d  /system/lib/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+88)
  native: #02 pc 00063a99  /system/lib/libc.so (pthread_cond_wait+32)
  native: #03 pc 0037cb2b  /system/lib/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::postAndWait()+174)
  native: #04 pc 0037ca59  /system/lib/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::drawFrame()+24)
  at android.view.ThreadedRenderer.nSyncAndDrawFrame(Native method)
  at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:823)
  at android.view.ViewRootImpl.draw(ViewRootImpl.java:3316)
  at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3120)
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2489)
  at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1465)
  at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7188)
  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
  at android.view.Choreographer.doCallbacks(Choreographer.java:761)
  at android.view.Choreographer.doFrame(Choreographer.java:696)
  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
  at android.os.Handler.handleCallback(Handler.java:873)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:193)
  at android.app.ActivityThread.main(ActivityThread.java:6718)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

以上的 trace 文件來自 systemui ANR,可以看出在 main 線程執行 NATIVE 方法的過程中,nSyncAndDrawFrame GPU 渲染的過程發生超時。

5. 如何避免和解決 ANR

  1. UI 線程儘量只做跟 UI 相關的工作
  2. 耗時的工作(比如數據庫操作,I/O,連接網絡或者別的有可能阻礙 UI 線程的操作)把它放入單獨的線程處理
  3. 儘量用 Handler 來處理 UIthread 和別的線程之間的交互
  4. 實在繞不開主線程,可以嘗試通過 Handler 延遲加載
  5. 廣播中如果有耗時操作,建議放在 service 中去執行,或者通過 handlerThread 分發執行。
分析着重點
  • cpu 佔用率方面
    • 可以通過分析各進程的 CPU 時間佔用率,來判斷是否爲某些進程長期佔用 CPU 導致該進程無法獲取到足夠的 CPU 處理時間,而導致 ANR
    • 重點關注下 CPU 的負載,即 Logcat 中 Load1(Load: 0.42 / 0.27 / 0.25,即 cpu 平均負載),各個進程總的 CPU 時間佔用率,用戶 CPU 時間佔用率,核心態 CPU 時間佔用率,以及 iowait CPU 時間佔用率。
  • 內存方面
    • 主要看當前應用 native 和 dalvik 層內存使用情況
    • 結合系統給每個應用分配的最大內存來分析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章