ANR 全稱Application Not Responding(應(yīng)用程序無響應(yīng)),一般頁面卡頓時間超過(一般是5秒)一定時間就會出現(xiàn)ANR對話款。Logcat一般會發(fā)現(xiàn)ANR以及traces.txt等字樣。出現(xiàn)ANR主要原因是因為我們在主線程中做了太多耗時操作。
A、ANR產(chǎn)生的原因
只有當(dāng)應(yīng)用程序的UI線程相應(yīng)超時才會引起ANR,超時產(chǎn)生原因一般有兩種
(1)當(dāng)前的事件沒有機(jī)會得到處理,例如UI線程正在響應(yīng)另外一個事件,當(dāng)前事件由于某種原因被阻塞了。
(2)當(dāng)前事件正在處理,但是由于耗時太長沒能及時完成
根據(jù)ANR產(chǎn)生的原因不同,超時事件也不盡相同,從本質(zhì)上講,產(chǎn)生ANR的原因有三種,大致可以對應(yīng)到Android 中四大組件中的三個(activity/view,BroadcastReceiver和service)。
(1)KeyDispatchTimeOut類型
最常見的一種類型,原因是View的按鍵事件或觸摸事件在特定的事件(5秒)內(nèi)無法得到響應(yīng)
(2)BroadcastTimeOut類型
原因是BroadcastReciver的onReceive()函數(shù)運行在了主線程中,在特定的事件(10秒)內(nèi)無法完成處理
(3)ServiceTimeOut類型
比較少出現(xiàn)的一種類型,原因是Service的各個生命周期函數(shù)在特定時間(20秒)內(nèi)無法完成處理
B、典型的ANR問題場景
(1)場景一:
應(yīng)用程序UI線程存在耗時操作,例如在UI線程中進(jìn)行網(wǎng)絡(luò)請求,數(shù)據(jù)庫操作或者文件操作等,可能會導(dǎo)致UI線程無法及時處理用戶輸入等。當(dāng)然在Android4.0之后,如果在UI線程中進(jìn)行網(wǎng)絡(luò)操作,將會拋出NetworkOnMainTreadException異常。
(2)場景二:
應(yīng)用程序的UI線程等待子線程釋放某個鎖,從而無法處理用戶的輸入。
(3)場景三:
耗時的動畫需要大象的計算工作,可能會導(dǎo)致CPU負(fù)載過重
C、ANR的定位和分析
當(dāng)發(fā)生ANR時,可以通過結(jié)合Logcat日志和生成的位于手機(jī)內(nèi)部存儲的/data/anr/traces.txt文件進(jìn)行分析和定位。
(1)Logcat日志信息
查看日志
(2)traces.txt日志信息
有助于問題已定位的信息主要內(nèi)容
a、發(fā)生ANR的進(jìn)程名稱、ID,以及時間。
b、其他

c、主工程基本信息

d、主線程的詳細(xì)信息

e、線程的調(diào)度信息

f、線程的上下文信息

g、線程的堆棧信息

D、ANR的避免和檢測
(1)不要在主線程中做耗時操作
為了避免開發(fā)中可能發(fā)生的ANR的問題,除了切記不要再主線程中做耗時操作,我們也可以借助一些工具來進(jìn)行檢測,從而更有效的避免ANR的引入
(2)StrictMode(代碼檢測)
嚴(yán)格模式StrictMode 是Android SDK提供的一個用來檢測代碼是否存在違規(guī)操作的工具類,StrictMode 主要檢測兩大類為題
a、線程策略ThreadPolicy
檢測可能存在的主線程耗時操作,解決這些檢測到的問題能夠減少應(yīng)用發(fā)生的ANR的概率。需要注意的是我們只能在DEbug版本中使用它,發(fā)布到市場版本必須關(guān)掉。

b、虛擬機(jī)策略VmPolicy
QQ圖片20180921175827.png

c、StrictMode使用
使用很簡單,我們只需要在應(yīng)用初始化的地方例如Application或者M(jìn)ainActivity類中onCreate方法中執(zhí)行如下代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
/**開啟線程模式*/
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
/**開啟虛擬機(jī)模式*/
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr);
}
調(diào)用detectAll表示檢測所有的檢測策略,我們也可以根據(jù)應(yīng)用需求只開啟一些策略
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
/**開啟線程模式,某些策略*/
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectCustomSlowCalls()
.penaltyLog()
.build());
/**開啟虛擬機(jī)模式,某些策略*/
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedRegistrationObjects()
.penaltyLog()
.build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr);
}
BlockCanary(非侵入式性能監(jiān)控函數(shù)庫)
BlockCanary是一個非侵入時的性能監(jiān)控函數(shù)庫,他的用法和LeakCanary類似,只不過LeakCanary檢測內(nèi)存泄漏,而BlockCanary主要用來監(jiān)控應(yīng)用主線程的卡頓,它的基本原理利用主線程的消息隊列處理機(jī)制,通過對比消息分發(fā)開始和結(jié)束的事件點來判斷是否超過設(shè)定的時間,如果是,則判斷為主線程卡頓。
集成順序:
a、build.gradle添加依賴
//主線程卡頓監(jiān)控依賴
implementation 'com.github.moduth:blockcanary-android:1.5.0'
//如果僅在debug 包啟用BlockCanary 進(jìn)行卡頓監(jiān)控和提示的話,這樣寫
//debugCompile 'com.github.moduth:blockcanary-android:1.5.0'
//releaseCompile 'com.github.moduth:blockcanary-no-op:1.5.0'
b、在Application中調(diào)用
@Override
public void onCreate() {
super.onCreate();
/**初始化調(diào)用*/
BlockCanary.install(this,new AppBlockCanaryContext()).start();
}
public class AppBlockCanaryContext extends BlockCanaryContext{
/**
* 實現(xiàn)各種上下文,包括應(yīng)用標(biāo)識符、用戶uid、網(wǎng)絡(luò)類型、卡曼判斷閾值、Log保存位置等
*/
}