博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ANR详细介绍
阅读量:6161 次
发布时间:2019-06-21

本文共 6544 字,大约阅读时间需要 21 分钟。

目录介绍

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

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:
  • 如果觉得好,可以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机制实现的原理可以先看这篇文章,个人觉得写的十分不错,可以看看:
  • 大概原理如下:
    • 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.关于博客汇总链接

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

02.关于我的博客

  • 我的个人站点:www.yczbj.org,www.ycbjie.cn
  • github:
  • 知乎:
  • 简书:
  • csdn:
  • 喜马拉雅听书:
  • 开源中国:
  • 泡在网上的日子:
  • 邮箱:yangchong211@163.com
  • 阿里云博客: 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:

项目地址:github.com/yangchong211

你可能感兴趣的文章
python了解
查看>>
在写HTML和CSS时的黄金规范
查看>>
【php】用filter_var实现的简单参数验证
查看>>
【转载】(EM算法)The EM Algorithm
查看>>
js调用.net后台事件,和后台调用前台等方法总结
查看>>
zookeeper的 目录加密
查看>>
Nested Loop,Sort Merge Join,Hash Join
查看>>
Webstrom快捷键
查看>>
BZOJ 2721: [Violet 5]樱花
查看>>
生成某一文件夹内文件清单(批量处理)
查看>>
【转】 SLIC超像素分割详解(一):简介
查看>>
PostgreSQL 数组类型
查看>>
编写一个函数,输入n为偶数时,调用方法求1/2+1/4+...+1/n,当输入n为奇数时,调用函数1/1+1/3+...+1/n...
查看>>
拉马车
查看>>
14.查看信息深入讲解
查看>>
AngularJS(6)-选择框Select
查看>>
初识shell脚本
查看>>
uva 11401思维+预处理
查看>>
9. Palindrome Number
查看>>
android-远程图片获取和本地缓存
查看>>