ANR即Application Not Responding應用無響應,通常在ANR的時候會彈出一個應用無響應對話框。也許有些開發者在使用某些手機開發中不在彈出應用無響應彈出框,特別是國產手機Android4.0以上的系統中,即便在開發者選項中設置了「顯示全部應用無響應-爲後臺應用顯示無響應ANR對話框」,主要是由於在某些國產手機系統中就將該選項屏蔽了,應用超過了必定時間無響應也不會彈出ANR對話框了。html
通常狀況下應用無響應的時候回產生一個日誌文件,位於/data/anr/文件夾下面,trace文件是Android Davik虛擬機在收到異常終止信號時產生的,最多見的一個觸發條件就是Android應用中產生了FC(force close)。因爲該文件的產生是在DVM中的,因此只有運行DVM實例的進程才能產生該文件,也就是說只有Java代碼才能產生該文件,App應用的Native層(如Android Library、用c/c++編譯的庫)即便異常也不會產生ANR日誌文件。咱們能夠經過ANR產生的traces日誌文件分析應用在哪裏產生了ANR,以此來有效解決應用中的ANR。java
在Android裏,應用程序的響應是由ActivityManager和WindowManager服務系統服務監視的,當檢測到下面三種狀況的任何一種時,Android就會針對特定的應用程序顯示ANR對話框。linux
形成ANR的緣由有不少,不管是在Activity或者BroadcastReceiver仍是在Service,咱們看到都是在主線程中操做引發的ANR,所以咱們應該避免在主線程作太多耗時的操做,網絡請求不用說了,Android4.0之後就禁止在主線程成執行請求了,除此以外就是要注意以下幾個方面:android
若是拉取traces.txt日誌文件c++
當產生ANR的時候系統會生成一個日誌文件,日誌存放在/data/anr/文件夾下面,通常名稱爲traces.txt,可是也有例外的,以下:sql
若是手機已是徹底root的了,能夠直接經過DDMS的File Explorer直接導出來,若是不是root的手機,能夠經過以下adb命令查看ANR日誌文件位於哪裏。shell
adb shell ls /data/anr/數據庫
而後經過adb的pull將日誌文件拉取到指定的路徑。網絡
adb pull /data/anr/traces.txt d:/多線程
可是若是手機沒有進行root,執行adb pull命令就會出現以下提示:
remote object ‘/data/anr/traces.txt’ does not exist
這時候咱們可使用adb將文件copy一份到sdcard,而後再拉取出來。
adbshell
cat /data/anr/traces.txt >/mnt/sdcard/traces.txt exit
traces.txt日誌文件分析
通常traces.txt日誌輸出格式以下,本實例是在主線程中強行Sleep致使的ANR日誌:
DALVIKTHREADS :
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0) "main" prio=5 tid=1 Sleeping | group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800 | sysTid=2957 nice=0 cgrp=default sched=0/0 handle=0xf7770ea0 | state=S schedstat=( 107710942 40533261 131 ) utm=4 stm=6 core=2 HZ=100 | stack=0xff49d000-0xff49f000 stackSize=8MB | heldmutexes= atjava.lang.Thread.sleep!(Native method) - sleepingon <0x31fd6f5d> (a java.lang.Object) atjava.lang.Thread.sleep(Thread.java:1031) - locked <0x31fd6f5d> (a java.lang.Object) atjava.lang.Thread.sleep(Thread.java:985) atcom.sunny.demo.MainActivity.startMethod(MainActivity.java:21) atjava.lang.reflect.Method.invoke!(Native method) atjava.lang.reflect.Method.invoke(Method.java:372) atandroid.view.View$1.onClick(View.java:4015) atandroid.view.View.performClick(View.java:4780) atandroid.view.View$PerformClick.run(View.java:19866) atandroid.os.Handler.handleCallback(Handler.java:739) atandroid.os.Handler.dispatchMessage(Handler.java:95) atandroid.os.Looper.loop(Looper.java:135) atandroid.app.ActivityThread.main(ActivityThread.java:5254) atjava.lang.reflect.Method.invoke!(Native method) atjava.lang.reflect.Method.invoke(Method.java:372) atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
經過traces.txt中 at com.sunny.demo.MainActivity.startMethod(MainActivity.java:21) 很容易就能夠定位到咱們的問題所在,MainActivity的第21行,而後咱們能夠看到代碼:
try {
Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); }
這就是因爲主線程睡眠10s致使的沒法響應。開發中定位ANR問題日誌有個很簡單的規律,就是 直接找到咱們本身開發App所使用的包名(包括第三方Library庫)信息開始定位找就能夠了 。
再看另一個實例,這是在開發中實際遇到的一個問題。
DALVIKTHREADS (23):
"main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800 | sysTid=2929 nice=0 cgrp=default sched=0/0 handle=0xf77bbea0 | state=R schedstat=( 42455718229 4950682716 114556 ) utm=208 stm=4036 core=1 HZ=100 | stack=0xff38d000-0xff38f000 stackSize=8MB | heldmutexes= atandroid.database.sqlite.SQLiteConnection.nativeExecute(Native method) atandroid.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:555) atandroid.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:437) atandroid.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:401) atandroid.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:522) atcom.xxx.xxxx.db.dao.DownloadItemDao$2.execute(DownloadItemDao.java:171) atcom.xxx.xxxx.db.DBManager.executeTask(DBManager.java:44) atcom.xxx.xxxx.db.dao.DownloadItemDao.updateDownload(DownloadItemDao.java:154) atcom.xxx.xxxx.download.DownloadManager$ManagerListener.onUIProgress(DownloadManager.java:222) atcom.sunny.net.listener.impl.UIProgressListener$UIHandler.progress(UIProgressListener.java:30) atcom.sunny.net.listener.handler.ProgressHandler.handleMessage(ProgressHandler.java:47) atandroid.os.Handler.dispatchMessage(Handler.java:102) atandroid.os.Looper.loop(Looper.java:135) atandroid.app.ActivityThread.main(ActivityThread.java:5254) atjava.lang.reflect.Method.invoke!(Native method) atjava.lang.reflect.Method.invoke(Method.java:372) atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
先定位數據庫操做處的問題,在DownloadItemDao的updateDownload()方法的154行,代碼以下:
而後接着定位executeTask方法,方法定義以下:
從這裏能夠看出來在下載過程當中咱們對數據庫的操做都是在Main線程中進行的,因此咱們能夠再定義一個異步的方法,將DownloadItemDao的updateDownload()方法中調用改成DBManager.getInstance().executeAsyncTask()的方式,這樣咱們將對數據庫這種比較耗時的操做子線程執行異步操做。
接着看一下下載中的回調方式at com.sunny.net.listener.handler.ProgressHandler.handleMessage(ProgressHandler.java:47),在ProgressHandler類的47行,咱們看到這裏是將子線程異步下載的信息實時回調到主線程的地方:
if (!progressModel.isDone()) {
progress(uiProgessListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
}
這裏的實現方式毫無疑問是實時回調進度,在UI展示層進度能夠沒必要實時回調,能夠減小對頻繁UI更新操做,從分析能夠知道頻繁更新UI也是致使ANR的一個很重要的緣由,所以能夠適當的採用必定的延時再執行UI回調,更改後代碼以下:
public static final int MIN_RATE=1000; long currTime = SystemClock.uptimeMillis(); if (currTime - lastUpdateTime >= MIN_RATE&&!progressModel.isDone()) { lastUpdateTime = currTime; progress(uiProgessListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone()); }
這樣經過ANR日誌文件能夠很是方面快捷的定位到問題所在,並及時的給出解決方式提升平常開發中對問題的處理效率。