Android ANR產生的原因以及其定位分析
前言
ANR是Android中一個獨有的概念,它的全稱是Application Not Responding(應用程式無響應)。
相信從事Android開發的同學,或多或少都遇到過,對於高質量的程式碼,ANR在開發者自測過程中可能不會經常遇到,但一旦測試人員進行Monkey測試,ANR出現的概率就比較高了,如何快速分析定位並解決,是開發者的必修課。
ANR的直觀體驗是使用者在操作APP的過程中,感覺介面卡頓,比如按下某個按鈕,開啟某個頁面等,當卡頓超過一定時間(一般是5秒)時就會出現ANR對話方塊,如下圖所示:
這時檢視Logcat,一般可以發現ANR以及traces.txt等字樣。可以發現,出現ANR主要是因為我們在主執行緒中做了耗時操作。這時你可以選擇“等待”按鈕,等待應用程式結束主執行緒耗時操作,或者選擇“確定”按鈕,結束這個應用程式。
1、ANR產生的原因
只有當應用程式的UI執行緒響應超時才會引起ANR,超時產生原因一般有兩種:
· 當前的事件沒有機會得到處理,列如UI執行緒正在響應另外一個事件,當前事件由於某種原因被阻塞了。
· 當前的事件正在處理,但是由於耗時太長沒能及時完成。
根據ANR產生的原因不同,超時時間也不盡相同,從本質上講,產生ANR的原因有三種,大致可以對應到Android中四大元件中的三個(Activity/View、BroadcastReceiver和Service)。
KeyDispatchTiemout
- 1
- 2
- 1
最常見的一種型別,原因是View的按鍵事件或者觸控事件在特定的時間(5秒)內無法得到響應。
BroadcastTiemout
- 1
- 2
- 1
原因是BroadcastReceiver的onReceive()函式執行在主執行緒中,在特定的時間(10秒)內無法完成處理。
ServiceTiemout
- 1
- 2
- 1
比較少出現的一種型別,原因是Service的各個生命週期函式在特定時間(20秒)內無法完成處理。
2、典型的ANR問題場景
· 應用程式UI執行緒存在耗時操作,例如在UI執行緒中進行網路請求、資料庫操作或者檔案操作,可能會導致UI執行緒無法及時處理使用者輸入等。當然在Android4.0之後,如果在UI執行緒中進行網路操作,將會丟擲NetworkOnMainThreadException異常。· 應用程式的UI執行緒等待子執行緒釋放某個鎖,從而無法處理使用者的輸入。
· 耗時的動畫需要大量的計算工作,可能導致CPU負載過量。
3、ANR的定位和分析
當發生ANR時,開發者可以通過結合Logcat日誌和生成的位於手機內部儲存的/data/anr/traces.txt檔案進行定位和分析。
4、ANR的避免和檢測
為了避免在開發中引入可能導致應用發生ANR的問題,除了切記不要在主執行緒中作耗時操作,我們也可以藉助於一些工具來進行檢測,從而更有效的避免ANR的引入。
4.1 StrictMode
嚴格模式StrictMode是Android SDK提供的一個用來檢測程式碼中是否存在違規操作的工具類,StrictMode主要檢測兩大類問題:
· 執行緒策略ThreadPolicy
- detectCustomSlowCalls: 檢測自定義耗時操作。
- detectDiskReads: 檢測是否存在磁碟讀取操作。
- detectDiskWrites: 檢測是否存在磁碟寫入操作。
- detectNetwork: 檢測是否存在網路操作。
· 虛擬機器策略VmPolicy
- detectActivityLeaks: 檢測是否存在Activity洩漏。
- detectLeakedClosableObjects: 檢測是否存在未關閉的Closeable物件洩漏。
- detectLeakedSqlLiteObjects: 檢測是否存在Sqlite物件洩漏。
- setClassInstanceLimit: 檢測類例項個數是否超過限制。
4.2 BlockCanary
BlockCanary是一個非侵入式的效能監控函式庫,它的用法和LeakCanary類似,只不過LeakCanary監控應用的記憶體洩漏,而BlockCanary主要用來監控應用主執行緒的卡頓。它的基本原理是利用主執行緒的訊息佇列處理機制,通過對比訊息分發開始和結束的時間點來判斷是否超過設定的時間,如果是,即判斷為主執行緒卡頓。它的整合很簡單,首先在build.gradle中新增線上依賴,如下:
dependencies {
compile 'com.github.moduth:blockcanary-android:1.2.1'
// 僅在debug包中啟用BlockCanary進行卡頓監控和提示的話,可以這麼用
debugCompile 'comgithub.moduth:blockcanary-android:1.2.1'
releaseCompile 'comgithub.moduth:blockcanary-no-op:1.2.1'
}
然後在Application類中進行配置和初始化即可,
public class DemoApplication extends Application {
@Override
public void onCreate() {
// 在主程序初始化呼叫
BlockCanary.install(this, new AppBlockCanaryContext()).start());
}
}