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就是这样的:
      • image
  • 总结,就是两个问题
    • 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

相关文章
相关标签/搜索