一、ANR问题简介
ANR是Application NotResponding的缩写。在Android系统中,ActivityManagerService(AMS)和WindowManagerService(WMS)会监测应用程序的响应时间,如果应用程序主线程(即UI线程)在规定时间内没有处理输入事件,或者对特定操作没有执行完毕,就会出现ANR。
触发ANR问题的三个直接原因:
1. 主线程没有在5秒内处理完按键、触屏等输入事件
2. 主线程没有在10秒内处理完一个Broadcast
3. 主线程没有在20秒内处理完Service启动的各个生命周期函数
第一种情况系统会弹提示对话框,第二、三种情况不弹对话框,只输出Log。这三种情况是引发ANR问题的直接原因,根本原因需要结合traces信息进行分析,引起ANR问题的根本原因可以大致归纳为以下两类:
应用进程自身原因,例如
1. 应用进程自身原因,例如
a) 主线程阻塞、挂起、死循环
b) 进程内其他线程CPU占用率高,使主线程无法抢占到CPU时间片
2. 其他应用进程原因,例如:
a) 当前应用进程进行进程间通信请求其他进程,其他进程的操作长时间没有反馈
b) 其他进程CPU占用率高,是当前进程无法抢占到CPU时间片
二、输入事件ANR分析
以输入事件ANR为例,说明ANR产生的大致流程,首先需要了解Android输入系统,它大致工作原理如下图所示:原始输入事件由EventHub读取,经过InputReader加工,再传给InputDispatcher进行派发。InputDispatcher运行在系统进程(进程名为system_server)的一个单独的线程中,应用程序的主线程在处理事件的过程中,InputDispatcher会不断的检测处理过程是否超时,一旦超时,会通过一系列的回调通知WMS的notifyANR函数,最终会调用到AMS中mHandler对象里的SHOW_NOT_RESPONDING_MSG这个case,此时界面上就显示ANR对话框,同时在logcat中打出ANR相关的信息,接着向应用进程发送一个SIGNAL_QUIT(3)信号,应用进程中的Signal Catcher线程负责处理这个信号,在/data/anr/traces.txt中打印应用进程中所有线程当前的调用栈信息。当前发生ANR的进程会第一个向traces文件中写入信息,反过来说,traces文件中出现的第一个进程正常情况下就是发生ANR的那个进程。
见外面附件。
线程状态主要包括以下这些:
Thread.java中定义的状态 | Thread.cpp中定义的状态 | 说明 |
TERMINATED | ZOMBIE | 线程死亡,终止运行 |
RUNNABLE | RUNNING/RUNNABLE | 线程可运行或正在运行 |
TIMED_WAITING | TIMED_WAIT | 执行了带有超时参数的wait、sleep或join函数 |
BLOCKED | MONITOR | 线程阻塞,等待获取对象锁 |
WAITING | WAIT | 执行了无超时参数的wait函数 |
NEW | INITIALIZING | 新建,正在初始化,为其分配资源 |
NEW | STARTING | 新建,正在启动 |
RUNNABLE | NATIVE | 正在执行JNI本地函数 |
WAITING | VMWAIT | 正在等待VM资源 |
RUNNABLE | SUSPENDED | 线程暂停,通常是由于GC或debug被暂停 |
| UNKNOWN | 未知状态 |
四、ANR问题预防与解决
预防:ANR问题发生在主线程,所以主线程中进行的所有耗时操作都可能引发ANR问题,这些耗时操作包括但不限于:联网、磁盘IO(读写文件,解析json文件,Decode一张图片等)、死锁、死循环、进程间通信(获取系统信息、获取编辑框内容)等。运行在主线程的方法包括:Activity、Service的各生命周期函数;BroadcastReceiver的onReceive方法;Handler的handleMessage方法;输入法各生命周期函数;Touch事件的处理;View的Measure绘制等等,所以不要在这些地方出现耗时的操作。
解决:把耗时操作放入子线程中,等处理完后通过Handler通知主线程。另外对于子线程要做好线程生命周期的管理,不能new了一个Thread对象,调用其start函数启动后就不再管理了,这样有潜在的“线程泄漏”风险,大量的线程本身就会抢占CPU资源,影响整个应用性能,要在适当的时候(例如Activity销毁时)考虑停止子线程。另外对于部分不便放入子线程的操作(比如获取编辑框内容等)可以考虑通过减少调用次数,来达到降低ANR概率的目的。