ANR详细介绍

目录介绍
  • 1.ANR简单介绍
  • 2.ANR发生场景
  • 3.ANR发生的原理
  • 4.ANR有哪些具体案例
  • 5.ANR具体如何分析
  • 6.解决方案
  • 7.ANR问题解答

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:github.com/yangchong21…
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

1.ANR简单介绍

  • 1.1 什么是ANR
    • ANR Activity not responding(页面没有响应)
    • ANR Application not responding 应用没有响应
    • Android 在4.0之后强制规定 访问网络必须开启子线程
    • 如果在主线程访问网络,4.0之后的系统就会抛出:android.os.NetworkOnMainThreadException 在主线程联网的异常
    • 联网的时候一定要在子线程操作,只要是耗时的操作,可能会把主线程阻塞住的操作,都要放到子线程里
    • 主线程(UI线程)16ms,刷新一次界面,一秒60次,60贞/秒
    • ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。
  • 1.2 ANR的产生需要满足三个条件
    • 主线程:只有应用程序进程的主线程响应超时才会产生ANR;
    • 超时时间:产生ANR的上下文不同,超时时间也会不同,但只要在这个时间上限内没有响应就会ANR;
    • 输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数,产生ANR的上下文不同,导致ANR的原因也会不同;

2.ANR发生场景

  • 主线程,被阻塞5秒钟以上,就会抛出ANR对话框。不同的组件发生ANR的时间不一样,Activity是5秒,BroadCastReceiver是10秒,Service是20秒(均为前台)。
  • 点击事件(按键和触摸事件)5s内没被处理: Input event dispatching timed out
  • service 前台20s后台200s未完成启动 Timeout executing service
    • Service Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。
    • 对于Service有两类:
      • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
      • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
  • BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s):Timeout of broadcast BroadcastRecord
    • 以BroadcastReviever为例,在onRecieve()方法执行10秒内没发生第一种ANR(也就是在这个过程中没有输入事件或输入事件还没到5s)才会发生Receiver timeout,否则将先发生事件无相应ANR,所以onRecieve()是有可能执行不到10s就发生ANR的,所以不要在onRecieve()方法里面干活
  • ContentProvider的publish在10s内没进行完:timeout publishing content providers
  • 思考一下,比如service前台是20秒,后台是200秒没响应会导致ANR,那么这个时间是哪里来的呢?
    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;
    
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
    
    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
    
        void serviceTimeout(ProcessRecord proc) {
        String anrMessage = null;
    
        synchronized(mAm) {
            if (proc.executingServices.size() == 0 || proc.thread == null) {
                return;
            }
            final long now = SystemClock.uptimeMillis();
            final long maxTime =  now -
                    (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
            ServiceRecord timeout = null;
            long nextTime = 0;
            for (int i=proc.executingServices.size()-1; i>=0; i--) {
                ServiceRecord sr = proc.executingServices.valueAt(i);
                if (sr.executingStart < maxTime) {
                    timeout = sr;
                    break;
                }
                if (sr.executingStart > nextTime) {
                    nextTime = sr.executingStart;
                }
            }
            if (timeout != null && mAm.mLruProcesses.contains(proc)) {
                Slog.w(TAG, "Timeout executing service: " + timeout);
                StringWriter sw = new StringWriter();
                PrintWriter pw = new FastPrintWriter(sw, false, 1024);
                pw.println(timeout);
                timeout.dump(pw, "    ");
                pw.close();
                mLastAnrDump = sw.toString();
                mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
                anrMessage = "executing service " + timeout.shortName;
            } else {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_TIMEOUT_MSG);
                msg.obj = proc;
                mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                        ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
            }
        }
    
        if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    }
    复制代码

3.ANR发生的原理

  • 关于ANR机制实现的原理可以先看这篇文章,个人觉得写的十分不错,可以看看:gityuan.com/2016/07/02/…
  • 大概原理如下:
    • 1.在进行相关操作调用hander.sendMessageAtTime()发送一个ANR的消息,延时时间为ANR发生的时间(如activity是当前时间5s之后)。
    • 2.进行相关的操作
    • 3.操作结束后向remove掉该条message。如果相关的操作在规定时间没有执行完成,该条message将被handler取出并执行,就发生了ANR。

4.ANR有哪些具体案例

  • Acitvity,Fragment中暴力相应点击事件有可能会导致ANR
  • 断点调试时,程序可能会出现ANR无限应
  • 主线程做了耗时操作,比如查询数据库数据导致ANR

5.ANR具体如何分析

  • ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:
    • 主线程在做一些耗时的工作导致线程卡死
    • 主线程被其他线程锁
    • cpu被其他进程占用,该进程没被分配到足够的cpu资源。
  • 然后看anr日志。千万别说不知道在哪里看日志,在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了/data/anr/traces.txt中
    • 从log中找到ANR反生的信息:会包含了ANR的时间、进程、是何种ANR等信息。
    • 在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:
      • 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。
      • 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑应用内一些代码不合理消耗掉了CPU资源,如出现了死循环或者后台有许多线程执行任务等等原因,这就要结合trace和ANR前后的log进一步分析了。
      • 如果CPU总用量不高,该进程和其他进程的占用过高,这有一定概率是由于某些主线程的操作就是耗时过长,或者是由于主进程被锁造成的。
    • 除了上述分析CPU usage之后,确定问题需要我们进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题最有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:
      • 主线程是running或者native而对应的栈对应了我们应用中的函数,则很有可能就是执行该函数时候发生了超时。
      • 主线程被block:非常明显的线程被锁,这时候可以看是被哪个线程锁了,可以考虑优化代码。如果是死锁问题,就更需要及时解决了。
      • 由于抓trace的时刻很有可能耗时操作已经执行完了(ANR -> 耗时操作执行完毕 ->系统抓trace),这时候的trace就没有什么用了,主线程的stack就是这样的:
  • 总结,就是两个问题
    • 1.CPU 问题
      • 在 Monkeylog.log 文件中定位到 "anr in" 位置,查看 cpu usage ,total 占用,如发现接近100%,暂时判断为 cpu 问题。
      • 然后在 logcat.log 文件中定位到 "not responding" 发生时间,并截取cpuinfo.log 中时间点前后 5s 的 log,然后计算 CPU 占中,看哪个进程用的多,在酌情分析模块的 CPU 占中。
    • 2.GC 问题
      • 定位到 logcat.log 文件中 "not responding" 发生时间点;
      • 去查看发生 ANR 时间点对应的 trace 文件,定位到应用报名,若Dalvik Thread主线程显示“SUSPENDED”,则为内存问题;
      • 截取 ANR 发生时间点前 5s 的 log,分析 "dalvikvm" 打印的 Paused GC 耗时,如果过多则定位为 GC 问题,需要查看这 5s 件发生了哪些耗时的操作。

6.解决方案

  • 将所有耗时操作,比如访问网络,Socket通信,查询大量SQL 语句,复杂逻辑计算等都放在子线程中去,然 后通过handler.sendMessage、runonUIThread、AsyncTask 等方式更新UI。无论如何都要确保用户界面作的流畅 度。如果耗时操作需要让用户等待,那么可以在界面上显示度条。
  • 使用AsyncTask处理耗时IO操作。在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的ANR风险,对于这种情况有时也可以用异步线程来执行相应的逻辑。另外, 要避免死锁的发生。
  • 使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREADPRIORITYBACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
  • 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
  • Activity的onCreate和onResume回调中尽量避免耗时的代码
  • BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。
  • 各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲,应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR

7.ANR问题解答

  • ANR有异常日志吗?或者说ANR在第三方崩溃日志中有日志吗?
    • 没有异常日志,因为本身不属于Error或者Exception

关于其他内容介绍

01.关于博客汇总链接
02.关于我的博客

项目地址:github.com/yangchong211

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值