什麼是ANR?
ANR是Application Not Responding的縮寫,即應用程序未響應。在android中,當你的應用程序在一段時間內響應不夠靈敏,即其界面線程處於阻塞狀態的時間過長,就會觸發ANR錯誤。
這個時候如果你的應用位於前臺,android系統會向使用者顯示一個對話框,用戶可以選擇“等待”而讓程序繼續運行,也可以選擇“強制關閉”。這種形式類似於windows系統中的“該程序未響應”。
如何去規避?
想要去規避ANR在你開發的android應用中的出現,那麼首先你得知道哪些情況會導致ANR的出現。可能我們見到的最多的答案就是:
- 輸入事件(按鍵和觸摸事件)在5s內沒被處理;
- BroadcastReceiver的onRecieve方法在10s內沒處理完;
- service的各個生命週期在20s內沒有執行完;
以上的答案其實沒什麼問題,只是不夠嚴謹,這裏的5s、10s、20s其實會受到手機自身性能的影響,實際測試的情況,不同的手機可能會有所偏差,而在廣播中只有前臺廣播需要在10s內處理完成,後臺廣播響應的最大時長爲60s。嚴格意義上來講還有一些其他的情況也可能會導致ANR,比如ContentProvider的publish在10s內沒進行完等。
總體上而言,當主線程做一些耗時的I/O操作、計算或者主線程處於阻塞狀態,又或者主線程處於死鎖狀態時都會導致我們的應用程序未響應。找到了產生ANR的條件,那麼如何去規避它就很簡單了,就是反着來嘛。這裏我們總結一下。
- 儘量避免應用在主線程中執行耗時的的I/O操作。
- 避免應用在主線程中進行長時間的計算工作。
- 主線程與另一個線程爭奪資源時,因儘量保證主線程優先執行,避免主線程處於阻塞狀態。
- 通過代碼避免主線程在進程中或通過 binder 調用與另一個線程之間發生的死鎖。
怎樣去檢測?
事無絕對,人無完人。我們不可能保證寫的每一行代碼中都不會導致ANR的出現,那麼一旦出現了ANR我們又該如何去找出原因呢。
Android Vitals(能有效的收集ANR的發生率)
這裏首先要介紹一下Android Vitals,Android Vitals是Google推出的一項計劃,旨在改善Android設備的穩定性和性能,當用戶選擇啓用該計劃時,其android設備會記錄各項指標,其中就有一項指標叫做ANR的發生率。開發者們可以很好的通過Google Play查看自身應用的Android Vitals各項指標,從而找到應用ANR的發生情況。當然,這些只是接入Google Play的福音,一般我們做國內的項目是沒法享受到的。
TraceView(拉去追蹤信息文件,分析錯誤報告)
TraceView是DDMS中自帶的一款圖形化顯示和分析跟蹤日誌的文件的工具。它也是我們檢測ANR的重要工具。首先我們可以通過按鈕或者代碼調用的方式捕捉你認爲可疑的代碼塊。
Debug.startMethodTracing("anr_trace"); //開始捕捉代碼塊
...
Debug.stopMethodTracing(); //結束
通過以上方式可以生成一個anr_trace.trace文件並將它保存到設備上(trace,txt文件存儲路徑由dalvik.vm.stack-trace-file系統屬性決定),這時我們可以使用adb命令(adb pull /sdcard /anr_trace.trace /tmp)將trace文件導出到電腦,然後放到DDMS中打開並分析它。通過這種方式我們可以很輕鬆的分析出那些在主程序中執行緩慢的代碼、死鎖、執行緩慢的廣播等能引發ANR的代碼,從而優化它。
這裏我們可以通過一個例子來直觀地分析,下面是一張直觀地分析圖。
可以看到截取的代碼塊是一段onClick方法,那麼這是一段主線程中的代碼,在這段代碼中存在一個sort排序。Incl Cpu Time表示當前方法執行時間,sort方法明顯用了8065.841毫秒即8s多。Incl Cpu Time%表示該方法在整個代碼塊耗時中所用耗時的百分比,這裏sort方法耗時也高達99.9%,可見是sort方法導致了主線程執行速度緩慢,從而可能會導致ANR。找到了原因,那麼解決辦法就呼之欲出了,正確的做法就是將sort排序放在工作線程中執行。
獲取ANR錯誤報告文件(當發生ANR時,系統會自動保存跟蹤信息)
這是一種非常直觀地分析ANR的方式,Android系統會在遇到ANR時存儲跟蹤信息。在較低的android版本中,應用發生ANR時,系統會將跟蹤信息存儲到/data/anr/traces.txt文件中。而在較新的版本中,則會有多個/data/anr/anr_*文件可供存儲。我們還是可以使用adb命令來從設備上拉取錯誤報告文件,然後打開並分析它。
StrictMode(嚴格模式)
StrictMode是一個開發者工具,常常被用來捕獲在應用主線程中發生的磁盤I/O、網絡訪問違例等行爲。
下面還是通過一個例子直觀地來體會,首先我們需要在應用程序中接入StrictMode。我們可以在application或者activity的onCreate方法中加入以下代碼。
//嚴格模式中的ThreadPolicy線程策略
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
//嚴格模式中的VmPolicy虛擬機策略
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //泄漏的Sqlite對象
.detectLeakedClosableObjects() //未關閉的Closable對象泄漏
.penaltyLog()
.penaltyDeath()
.build());
然後編寫一段違規的代碼。
final TextView tvHW = findViewById(R.id.tv_hw);
tvHW.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Debug.startMethodTracing("anr_trace");
try {
Thread.sleep(10*1000);
tvHW.setText("hello ledding");
} catch (InterruptedException e) {
e.printStackTrace();
}
Debug.stopMethodTracing();
}
});
最後,運行下代碼,通過logcat查看StrictMode捕捉的信息。
通過日誌可以看到是strictmode的DiskReadViolation(磁盤讀取操作)異常,總共用時10257ms,發生在onClick中。當然StrictMode的功能遠不止這些,它還能用來檢測代碼中的內存泄漏等,這裏也就不再延伸了。
總結
ANR是android一種常見的問題,也是android開發者不可避免的知識區,一個友好的app應該規避ANR現象的出現,從而大大的提高用戶的體驗度。掌握併合理的運用這些工具是解決ANR的利器。工欲善其事,必先利其器。