關於android ANR 問題的分析
ANR (Application Not Responding)
ANR定義:在Android上,如果你的應用程式有一段時間響應不夠靈敏,系統會向用戶顯示一個對話方塊,這個對話方塊稱作應用程式無響應(ANR:Application Not Responding)對話方塊。
預設情況下,在android中Activity的最長執行時間是5秒,BroadcastReceiver的最長執行時間則是10秒。
在Android裡,應用程式的響應性是由Activity Manager和WindowManager系統服務監視的
1.在5秒內沒有響應輸入的事件(例如,按鍵按下,螢幕觸控)
2.BroadcastReceiver在10秒內沒有執行完畢
3.service 前臺20s後臺200s未完成啟動 Timeout executing service
app自身程序主執行緒阻塞, 掛起, 死鎖導致 機器本身的cpu, 記憶體, io繁忙, 無法及時響應
造成以上幾點的原因有很多,比如在主執行緒中做了非常耗時的操作,比如說是下載,io異常等。
ANR機制的實現原理:
文章:http://gityuan.com/2016/07/02/android-anr/從原始碼角度詳細的分析了ANR機制實現的原理。對於上一章講到的1-4中情況,分別找到了其原始碼中是如何實現的,對於每一種大概原理如下:1.在進行相關操作呼叫hander.sendMessageAtTime()傳送一個ANR的訊息,延時時間為ANR發生的時間(如前臺Service是當前時間20s之後)。2.進行相關的操作3.操作結束後向remove掉該條message。如果相關的操作在規定時間沒有執行完成,該條message將被handler取出並執行,就發生了ANR。
如何避免ANR?
1、執行在主執行緒裡儘可能少做事情。特別是,Activity應該在它的關鍵生命週期方法(如onCreate()和onResume())裡儘可能少的去做建立操作。(可以採用重新開啟子執行緒的方式,然後使用Handler+Message的方式做一些操作,比如更新主執行緒中的ui等)
2、應用程式應該避免在BroadcastReceiver裡做耗時的操作或計算。但不再是在子執行緒裡做這些任務(因為 BroadcastReceiver的生命週期短),替代的是,如果響應Intent廣播需要執行一個耗時的動作的話,應用程式應該啟動一個 Service。(此處需要注意的是可以在廣播接受者中啟動Service,但是卻不可以在Service中啟動broadcasereciver,關於原因後續會有介紹,此處不是本文重點)
總結:anr異常也是在程式中自己經常遇到的問題,主要的解決辦法自己最常用的就是不要在主執行緒中做耗時的操作,而應放在子執行緒中來實現,比如採用Handler+mesage的方式,或者是有時候需要做一些和網路相互互動的耗時操作就採用asyntask非同步任務的方式(它的底層其實Handler+mesage有所區別的是它是執行緒池)等,在主執行緒中更新UI。
如何分析ANR問題:
從前文可以明確,ANR問題是由於主執行緒的任務在規定時間內沒處理完任務,而造成這種情況的原因大致會有一下幾點:
- 主執行緒在做一些耗時的工作
- 主執行緒被其他執行緒鎖
- cpu被其他程序佔用,該程序沒被分配到足夠的cpu資源。
判斷一個ANR屬於哪種情況便是分析ANR問題的關鍵。那麼拿到一個anr的日誌,應該如何分析呢?
在發生ANR的時候,系統會收集ANR相關的資訊提供給開發者:首先在Log中有ANR相關的資訊,其次會收集ANR時的CPU使用情況,還會收集trace資訊,也就是當時各個執行緒的執行情況。trace檔案儲存到了/data/anr/traces.txt中,此外,ANR前後該程序打印出的log也有一定價值。一般來說可以按一下思路來分析:
- 從log中找到ANR反生的資訊:可以從log中搜索“ANR in”或“am_anr”,會找到ANR發生的log,該行會包含了ANR的時間、程序、是何種ANR等資訊,如果是BroadcastReceiver的ANR可以懷疑BroadCastReceiver.onRecieve()的問題,如果的Service或Provider就懷疑是否其onCreate()的問題。
- 在該條log之後會有CPU usage的資訊,表明了CPU在ANR前後的用量(log會表明擷取ANR的時間),從各種CPU Usage資訊中大概可以分析如下幾點:
(1). 如果某些程序的CPU佔用百分比較高,幾乎佔用了所有CPU資源,而發生ANR的程序CPU佔用為0%或非常低,則認為CPU資源被佔用,程序沒有被分配足夠的資源,從而發生了ANR。這種情況多數可以認為是系統狀態的問題,並不是由本應用造成的。
(2). 如果發生ANR的程序CPU佔用較高,如到了80%或90%以上,則可以懷疑應用內一些程式碼不合理消耗掉了CPU資源,如出現了死迴圈或者後臺有許多執行緒執行任務等等原因,這就要結合trace和ANR前後的log進一步分析了。
(3). 如果CPU總用量不高,該程序和其他程序的佔用過高,這有一定概率是由於某些主執行緒的操作就是耗時過長,或者是由於主程序被鎖造成的。 - 除了上述的情況(1)以外,分析CPU usage之後,確定問題需要我們進一步分析trace檔案。trace檔案記錄了發生ANR前後該程序的各個執行緒的stack。對我們分析ANR問題最有價值的就是其中主執行緒的stack,一般主執行緒的trace可能有如下幾種情況:
(1). 主執行緒是running或者native而對應的棧對應了我們應用中的函式,則很有可能就是執行該函式時候發生了超時。
(2). 主執行緒被block:非常明顯的執行緒被鎖,這時候可以看是被哪個執行緒鎖了,可以考慮優化程式碼。如果是死鎖問題,就更需要及時解決了。
(3). 由於抓trace的時刻很有可能耗時操作已經執行完了(ANR -> 耗時操作執行完畢 ->系統抓trace),這時候的trace就沒有什麼用了,主執行緒的stack就是這樣的:
- "main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 obj=0x757855c8 self=0xb4d76500 | sysTid=3276 nice=0 cgrp=default sched=0/0 handle=0xb6ff5b34 | state=S schedstat=( 50540218363 186568972172 209049 ) utm=3290 stm=1764 core=3 HZ=100 | stack=0xbe307000-0xbe309000 stackSize=8MB | held mutexes= kernel: (couldn't read /proc/self/task/3276/stack) native: #00 pc 0004099c /system/lib/libc.so (__epoll_pwait+20) native: #01 pc 00019f63 /system/lib/libc.so (epoll_pwait+26) native: #02 pc 00019f71 /system/lib/libc.so (epoll_wait+6) native: #03 pc 00012ce7 /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi+102) native: #04 pc 00012f63 /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+130) native: #05 pc 00086abd /system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+22) native: #06 pc 0000055d /data/dalvik-cache/arm/[email protected]@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+96) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:323) at android.os.Looper.loop(Looper.java:138) at android.app.ActivityThread.main(ActivityThread.java:5528) at java.lang.reflect.Method.invoke!(Native method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:740) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:630)
當然這種情況很有可能是由於該程序的其他執行緒消耗掉了CPU資源,這就需要分析其他執行緒的trace以及ANR前後該程序自己輸出的log了。
如何降低ANR的概率:
有一些操作是很危險的,非常容易發生ANR,在寫程式碼時候一定要避免:
- 主執行緒讀取資料:在Android中主執行緒去讀取資料是非常不好的,Android是不允許主執行緒從網路讀資料的,但系統允許主執行緒從資料庫或者其他地方獲取資料,但這種操作ANR風險很大,也會造成掉幀等,影響使用者體驗。
(1). 避免在主執行緒query provider,首先這會比較耗時,另外這個操作provider那一方的程序如果掛掉了或者正在啟動,我們應用的query就會很長時間不會返回,我們應該在其他執行緒中執行資料庫query、provider的query等獲取資料的操作。
(2). sharePreference的呼叫:針對sharePreference的優化點有很多,文章http://weishu.me/2016/10/13/sharedpreference-advices/ 詳細介紹了幾點sharepreference使用時候的注意事項。首先sharePreference的commit()方法是同步的,apply()方法一般是非同步執行的。所以在主執行緒不要用其commit(),用apply()替換。其次sharePreference的寫是全量寫而非增量寫,所以儘量都修改完同一apply,避免改一點apply一次(apply()方法在Activity stop的時候主執行緒會等待寫入完成,提交多次就很容易卡)。並且儲存文字也不宜過大,這樣會很慢。另外,如果寫入的是json或者xml,由於需要加和刪轉義符號,速度會比較慢。 - 不要在broadcastReciever的onRecieve()方法中幹活,這一點很容易被忽略,尤其應用在後臺的時候。為避免這種情況,一種解決方案是直接開的非同步執行緒執行,但此時應用可能在後臺,系統優先順序較低,程序很容易被系統殺死,所以可以選擇開個IntentService去執行相應操作,即使是後臺Service也會提高程序優先順序,降低被殺可能性。
- 各個元件的生命週期函式都不應該有太耗時的操作,即使對於後臺Service或者ContentProvider來講,應用在後臺執行時候其onCreate()時候不會有使用者輸入引起事件無響應ANR,但其執行時間過長也會引起Service的ANR和ContentProvider的ANR。
- 儘量避免主執行緒的被鎖的情況,在一些同步的操作主執行緒有可能被鎖,需要等待其他執行緒釋放相應鎖才能繼續執行,這樣會有一定的ANR風險,對於這種情況有時也可以用非同步執行緒來執行相應的邏輯。另外, 我們要避免死鎖的發生(主執行緒被死鎖基本就等於要發生ANR了)。