一、什么是ANR
ANR(Application Not responding),是指应用程序未响应,Android 系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成 ANR。一般地,这时往往会弹出一个提示框,告知用户「当前 xxx 未响应」,用户可选择继续等待或者 Force Close。
二、什么情况会发生ANR
- 输入事件(按键和触摸事件)5s内没被处理。
- Service 前台20s后台200s未完成启动。
- BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s)。
- ContentProvider的publish在10s内没进行完。
1、输入事件
代码如下:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
delay();
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_delay:
handler.sendEmptyMessage(1);
break;
case R.id.btn_click:
Log.d(TAG, "doClick start");
delay();
Log.d(TAG, "doClick finish");
break;
}
操作步骤:
先点击延时按钮,然后会阻塞主线程。然后点击按钮2下,5秒后就会报ANR了。
2、Service
代码如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand start");
// service中超过20s就会报anr,放在子线程就不会报ANR
delay();
Log.d(TAG, "onStartCommand finish");
return Service.START_STICKY;
}
private void delay() {
Log.d(TAG, "delay start");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "delay finish");
}
在Service的onStartCommand()中开始延时操作,延时20秒时就会报ANR。
3、BroadcastReceiver
代码如下:
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive start");
delay();
Log.d(TAG, "onReceive finish");
}
private void delay() {
Log.d(TAG, "delay start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "delay finish");
}
在BroadcastReceiver的onReceive中加入延时操作。如果是前台广播为10s报ANR,如果是后台广播为60s报ANR。
默认发送广播的方式启动的就是后台广播。
发送广播时Intent加了FLAG_RECEIVER_FOREGROUND就是启动前台广播。代码如下:
Intent intent = new Intent();
intent.setAction("ANR_RECEIVER");
intent.setComponent(new ComponentName(AnrTestActivity.this, AnrReceiver.class));
// 加这个flag就是前台广播,否则是后台广播
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sendBroadcast(intent);
三、如何分析ANR
ANR产生场景:二.1的场景。使用sleep阻塞主线程,然后点击按钮产生ANR。
1、adb pull
在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了/data/anr/traces.txt中。我们可以通过adb pull data/anr/traces.txt将文件导出来。
然后查找线程名为main的线程的日志。日志内容如下:
通过上面的日志很容易发现产生ANR的原因就是AnrTestActivity中delay方法。
2、adb bugreport
但是adb pull data/anr/traces.txt命令要求是root的手机,现在很多手机不能root。因此我们需要换种方式获取ANR的日志。
使用adb bugreport命令生成一个zip包。
然后通过AS 的Device File Explorer将生成的文件保存在本地。
然后解压,使用如下文件:
搜索关键字“VM TRACES AT LAST ANR”找到ANR的日志如下:
然后在这下面找main线程的日志。
四、如何避免ANR
基本的思路就是将耗时操作在子线程来处理,减少其他耗时操作和错误操作。
1.使用 AsyncTask 处理耗时 IO 操作。
2.使用Thread 或者HandlerThread时,调用 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) 设置优先级,否则仍然会降低程序响应,因为默认 Thread 的优先级和主线程相同。
3.使用 Handler 处理工作线程结果,而不是使用 Thread.wait() 或者 Thread.sleep() 来阻塞主线程。
4.Activity 的 onCreate 和 onResume 回调中尽量避免耗时的代码
5.BroadcastReceive 中 onReceive 代码也要尽量减少耗时,建议使用 IntentService 处理。