anr發(fā)生原因
Application Not Responding(簡(jiǎn)稱:ANR)指應(yīng)用中一些特定的事件(如用戶觸摸事件豺裆、廣播等)在應(yīng)用的主線程沒(méi)有在規(guī)定的時(shí)間內(nèi)處理完柏副,系統(tǒng)自動(dòng)做出終止應(yīng)用運(yùn)行的響應(yīng)。Android系統(tǒng)中匀泊,ActivityManagerService(簡(jiǎn)稱AMS)和WindowManagerService(簡(jiǎn)稱WMS)會(huì)檢測(cè)App的響應(yīng)時(shí)間醇王,如果App在特定時(shí)間無(wú)法相應(yīng)屏幕觸摸或鍵盤輸入時(shí)間,或者特定事件沒(méi)有處理完畢,就會(huì)出現(xiàn)ANR原茅。
anr出現(xiàn)的場(chǎng)景:
InputDispatching Timeout:5秒內(nèi)無(wú)法響應(yīng)屏幕觸摸事件或鍵盤輸入事件牍陌。
BroadcastQueue Timeout :在執(zhí)行前臺(tái)廣播(BroadcastReceiver)的onReceive()函數(shù)時(shí)10秒沒(méi)有處理完成,后臺(tái)為60秒员咽。
Service Timeout :前臺(tái)服務(wù)20秒內(nèi)毒涧,后臺(tái)服務(wù)在200秒內(nèi)沒(méi)有執(zhí)行完畢。
ContentProvider Timeout :ContentProvider的publish在10s內(nèi)沒(méi)進(jìn)行完贝室。
1契讲、查看是否有anr文件
在命令行窗口
adb shell
ls adb /data/anr
2、導(dǎo)出anr日志
法1:
在android studio terminal窗口
adb bugreport
法2:
打開(kāi)CMD小黑框滑频,進(jìn)入到Android SDK 目錄下的platform-tools文件夾下面直接輸出 : adb logcat 復(fù)制粘貼到一個(gè)txt文本下捡偏,方便稍后查看
法3:
執(zhí)行adb pull /data/anr/traces.txt D:\traces.txt 輸出traces日志。
anr日志分析
例1
try {
Thread.sleep(9000);
} catch (InterruptedException e) {
e.printStackTrace();
}
在activity中按鈕點(diǎn)擊后在主線程執(zhí)行sleep操作峡迷。導(dǎo)出的anr日志如下:
搜索DALVIK_THREADS银伟,找到第一個(gè),然后看主線程main绘搞,tid是線程id彤避。然后線程狀態(tài)是Sleeping。繼續(xù)往下看堆椇幌剑可以看到是因?yàn)榘粹o點(diǎn)擊后sleep時(shí)間過(guò)長(zhǎng)琉预,導(dǎo)致后續(xù)點(diǎn)擊事件無(wú)法及時(shí)處理導(dǎo)致。
各參數(shù)含義:
group:線程所處的線程組
sCount: 線程被正常掛起的次數(shù)
dsCount: 線程因調(diào)試而掛起次數(shù)
nice:線程的調(diào)度有優(yōu)先級(jí)
utm:線程在用戶態(tài)中調(diào)度時(shí)間值
stm:線程在內(nèi)核態(tài)中的調(diào)度時(shí)間值
core:最后執(zhí)行這個(gè)線程的CPU核序號(hào)
例2
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private Button button;
private Button button2;
private Button btnMVTest;
private Object object= new Object();
private MyLock myLock = new MyLock();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
}
private void findViews() {
button = findViewById(R.id.button);
button2 = findViewById(R.id.button2);
btnMVTest = findViewById(R.id.btnMVTest);
button.setOnClickListener(this);
button2.setOnClickListener(this);
btnMVTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int viewId = v.getId();
switch (viewId){
case R.id.button2:
testBlock();
break;
case R.id.btnMVTest:
Log.e(TAG, "onClick: mainThread="+Thread.currentThread().getName()+" tid="+Thread.currentThread().getId());
doSqlQuery();
break;
}
}
private void testBlock(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock){
try {
doSqlQuery();
Thread.sleep(11000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.setName("DTestBlock");
thread.start();
}
private void doSqlQuery(){
synchronized (myLock){
Log.e(TAG, "doThreadBlcok: 被鎖住的函數(shù) threadName="+Thread.currentThread().getName()+" threadId="+Thread.currentThread().getId());
}
}
private class MyLock{
}
}
看下anr日志蒿褂。
可以看到主線程處于Blocked(阻塞)狀態(tài)圆米,為何阻塞:
- waiting to lock <0x0b19381d> (a com.example.router.MainActivity$MyLock) held by thread 3
嘗試鎖定MyLock實(shí)例對(duì)象,但是它被線程3所持有啄栓。
線程3執(zhí)行了sleep娄帖,在sleep的時(shí)間段內(nèi),沒(méi)有釋放對(duì)象鎖昙楚。導(dǎo)致主線程獲取不到對(duì)象鎖而處于阻塞狀態(tài)近速,后續(xù)點(diǎn)擊事件響應(yīng)不了,從而導(dǎo)致anr桂肌。
3数焊、總結(jié)
anr是比較嚴(yán)重的性能問(wèn)題,對(duì)用戶體驗(yàn)影響較大崎场。而且手動(dòng)導(dǎo)出anr日志有時(shí)并沒(méi)那么方便佩耳,可以考慮使用第三方性能檢測(cè)工具如bugly等,簡(jiǎn)化anr的排查谭跨。