一篇文章帮你搞定ANR

一、什么是ANR

ANR(Application Not responding),是指应用程序未响应,Android 系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成 ANR。一般地,这时往往会弹出一个提示框,告知用户「当前 xxx 未响应」,用户可选择继续等待或者 Force Close。

二、什么情况会发生ANR

  1. 输入事件(按键和触摸事件)5s内没被处理。
  2. Service 前台20s后台200s未完成启动。
  3. BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s)。
  4. 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 处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值