- ANR有哪些类型?
- 什么情况下会产生ANR?
- ANR如何解决?
ANR概念
ANR(Application Not Responding) 应用程序无响应。如果应用程序在UI线程被阻塞太长时间,就会出现ANR,通常出现ANR,系统会弹出一个提示提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。
出现ANR必须解决
ANR类型
KeyDispatchTimeout(常见)
input事件在5S内没有处理完成发生了ANR。
logcat日志关键字:Input event dispatching timed out
BroadcastTimeout
前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
logcat日志关键字:Timeout of broadcast BroadcastRecord
ServiceTimeout
前台Service:onCreate,onStart,onBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreate,onStart,onBind等生命周期在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service
ContentProviderTimeout
ContentProvider 在10S内没有处理完成发生ANR。
logcat日志关键字:timeout publishing content providers
为什么出现ANR
1:主线程频繁进行耗时的IO操作:如网络请求,数据库读写
UI线程进行潜在的耗时操作都很可能造成ANR,如网络请求,操作数据库、IO操作、复杂的layout、庞大的for循环、高耗时的计算(如改变位图尺寸)等。
这些耗时操作都应该在子线程中来完成,然而并不是说主线程阻塞在那里等待子线程的完成(也不是调用Thread.wait()或是Thread.sleep()),而是主线程应该为子线程提供给一个Handler或者使用AsyncTask等异步操作的方式,以便子线程完成操作时能够提交结果给主线程,主线程根据结果更新UI。以这种方式设计应用,将可以保证主线程保持对输入事件的响应的敏感性,并能避免由于输入事件的5秒超时引发的ANR对话框。
具体例子:
- (1)Realm数据库官方称速度非常快,可以在UI线程中操作,测试时的确没有问题,但是上线后,发现一大堆anr问题(项目接入bugly)。
- (2)在onlayout读写一个文件,用sysytrace测试执行时间,只有微秒级别,测试时没有任何问题,但是上线后一大堆anr问题。
- (3)主线程使用SharedPreference,测试时没有任何问题,但是上线后一大堆anr问题。
原因:
为什么测试时没有任何anr问题,一上线就报各种anr问题?
测试用的手机都是性能比较好的手机,而且手机没有安装各种一大堆软件,内存占用少,硬件性能好。而用户的手机可能是性能不大好的机子,而且可能还安装了大量的软件,手机速度下降等等。
总结:
凡是进行IO读写操作(数据库,文件,网络,序列化等),都不要在UI线程操作。
2:多线程操作的死锁,主线程被block;
并发编程导致的死锁引起的ANR,比如调用thread的join() / Sleep() / Wait() 或者等待lock的时候
3:主线程被 Binder 对端block;
Binder通信机制默认是同步的,会阻塞调用的线程,当然也有异步的oneway机制。
4:System Server中WatchDog出现ANR;
framework层的开发遇到的比较多。
5:service binder的连接数量达到上线无法和和System Server通信
Binder被占满导致主线程无法和SystemServer通信
6:系统资源已耗尽(管道、CPU、RAM、IO),无法获取到系统资源
大量的线程死循环的去做任务,导致应用获取不到CPU的时间片去处理用户输入事件。
ANR问题如何解决
线下开发过程中,如果出现ANR,Android Studio的logcat会有输出信息提示(但并不一定真的是那一行代码导致的anr,需要根据提示信息综合判断),并且还会在/data/anr/traces_*.txt 中保存有anr相关信息,结合logcat中的提示信息判断anr是由什么原因导致的。
traces_*.txt
*一般是firstPid,即发生anr的pid
ActivityManagerservice 中实现 通过appNotResponding(), dumpStackTraces() 两个主要方法来生成应用的anr
traces_SystemServer_WDT.txt(watchdog)
Watchdog中实现
system_server进程栈信息
traces.txt(dalvik.vm.stack-trace-file)
系统定义的默认trace文件路径
分析技巧
整体思路:
通过logcat日志和traces文件确认anr发生时间点
traces文件和CPU使用率
/data/anr/traces.txt
主线程状态
其他线程状态
如何根据logcat日志和traces文件分析anr?
1.根据anr的几种类型,在logcat日志中搜索对应的关键字。
比如:Input event dispatching timed out
Timeout of broadcast BroadcastRecord
Timeout executing service
timeout publishing content providers
2.在traces文件中搜索对应的关键字。
比如:waiting、held、held by等
比如搜索waiting关键字:
可以发现就是发送广播时造成的堵塞,引起的ANR。
再比如下面这种,io占用了CPU 87%:
一般就是在主线程进行IO操作引起的ANR。
关键信息
ANR时间:07-20 15:36:36.472
进程pid:1480
进程名:com.xxxx.moblie
ANR类型:KeyDispatchTimeout
main:main标识是主线程,如果是线程,那么命名成“Thread-X”的格式,x表示线程id,逐步递增。
prio:线程优先级,默认是5
tid:tid不是线程的id,是线程唯一标识ID
group:是线程组名称
sCount:该线程被挂起的次数
dsCount:是线程被调试器挂起的次数
obj:对象地址
self:该线程Native的地址
sysTid:是线程号(主线程的线程号和进程号相同)
nice:是线程的调度优先级
sched:分别标志了线程的调度策略和优先级
cgrp:调度归属组
handle:线程处理函数的地址。
state:是调度状态
schedstat:从 /proc/[pid]/task/[tid]/schedstat读出,三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度,不支持这项信息的三个值都是0;
utm:是线程用户态下使用的时间值(单位是jiffies)
stm:是内核态下的调度时间值
core:是最后执行这个线程的cpu核的序号
线程状态
THREAD_UNDEFINED = -1
THREAD_ZOMBIE = 0, /* TERMINATED /
THREAD_RUNNING = 1, / RUNNABLE or running now /
THREAD_TIMED_WAIT = 2,/ TIMED_WAITING Object.wait()
THREAD_MONITOR = 3, /* BLOCKED on a monitor /
THREAD_WAIT = 4, / WAITING in Object.wait() /
THREAD_INITIALIZING= 5, / allocated, not yet running /
THREAD_STARTING = 6, / started, not yet on thread list /
THREAD_NATIVE = 7, / off in a JNI native method /
THREAD_VMWAIT = 8, / waiting on a VM resource /
THREAD_SUSPENDED = 9, / suspended, usually by GC or debugger
Android线程状态
ANR监控方案
ANR是如何监控的?
一般有两种监控方案,一种是原生的,FileObserver:监控某个目录/文件的状态有没有发生改变,或者是在某个目录 有没有创建或者删除文件。
一般通过自定义类继承FileObserver:
class MyFileObserver extends FileObserver //路径传data/anr
这样当文件发生变化时说明有ANR产生(这时可以上传trace.txt文件到服务器用于分析anr),当文件发生相应的变化时会回调FileObserver 中的public void onEvent(int event, @Nullable String path)
方法。
另一种是,watchdog方案。
ANR监控方案1-watchdog
watchdog 和leakcanary原理很相似。
Watchdog.java
Watchdog有个假唤醒机制,while循环判断,如果还没有wait到足够的时间,那么继续wait:
//Watchdog.java
// NOTE: We use uptimeMillis() here because we do not want to increment the time we
// wait while asleep. If the device is asleep then the thing that we are waiting
// to timeout on is asleep as well and won't have a chance to run, causing a false
// positive on when to kill things.
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
这个小技巧在很多地方都有使用。
ANR监控方案2-FileObserver
Android系统在此基础上封装了一个FileObserver类来方便使用Inotify机制。FileObserver是一个抽象类,需要定义子类实现该类的onEvent抽象方法,当被监控的文件或者目录发生变更事件时,将回调FileObserver的onEvent()函数来处理文件或目录的变更事件
public class ANRFileObserver extends FileObserver {
private String TAG = "ANRFileObserver";
/**
*
* @param path 传 data/anr/ 这个目录
*/
public ANRFileObserver(String path) {
super(path);
}
public ANRFileObserver(String path, int mask) {
super(path, mask);
}
/**
* ANRFileObserver监控的路径/文件发生改变时会回调onEvent()方法,对应的各个事件类型如下
* @param event
* @param path
*/
@Override
public void onEvent(int event, @Nullable String path) {
switch (event) {
case FileObserver.ACCESS://文件被访问
Log.i(TAG, "ACCESS: " + path);
break;
case FileObserver.ATTRIB://文件属性被修改,如 chmod、chown、touch 等
Log.i(TAG, "ATTRIB: " + path);
break;
case FileObserver.CLOSE_NOWRITE://不可写文件被 close
Log.i(TAG, "CLOSE_NOWRITE: " + path);
break;
case FileObserver.CLOSE_WRITE://可写文件被 close
Log.i(TAG, "CLOSE_WRITE: " + path);
break;
case FileObserver.CREATE://创建新文件
Log.i(TAG, "CREATE: " + path);
break;
case FileObserver.DELETE:// 文件被删除,如 rm
Log.i(TAG, "DELETE: " + path);
break;
case FileObserver.DELETE_SELF:// 自删除,即一个可执行文件在执行时删除自己
Log.i(TAG, "DELETE_SELF: " + path);
break;
case FileObserver.MODIFY://文件被修改
Log.i(TAG, "MODIFY: " + path);
break;
case FileObserver.MOVE_SELF://自移动,即一个可执行文件在执行时移动自己
Log.i(TAG, "MOVE_SELF: " + path);
break;
case FileObserver.MOVED_FROM://文件被移走,如 mv
Log.i(TAG, "MOVED_FROM: " + path);
break;
case FileObserver.MOVED_TO://文件被移来,如 mv、cp
Log.i(TAG, "MOVED_TO: " + path);
break;
case FileObserver.OPEN://文件被 open
Log.i(TAG, "OPEN: " + path);
break;
default:
//CLOSE : 文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
//ALL_EVENTS : 包括上面的所有事件
Log.i(TAG, "DEFAULT(" + event + "): " + path);
break;
}
}
}
FileObserver会不会被selinux挡住?
Android 5.0之后肯定会。
实例分析ANR
实例1:
先看cpu:
CPU usage from 76016ms to 0ms ago (2020-01-01 13:06:16.027 to 2020-01-01 13:07:32.043):
...
26% TOTAL: 22% user + 3.6% kernel + 0% iowait + 0.1% softirq
从CPU使用数据可以看出不是因为CPU使用率高资源耗尽或者IO读写堵塞引起的ANR。
看是否是由于线程死锁导致的,可以在traces.txt文件中搜索 held 关键字:
suspend all histogram: Sum: 482us 99% C.I. 15us-232us Avg: 48.200us Max: 239us
DALVIK THREADS (13):
"main" prio=5 tid=1 Runnable
| group="main" sCount=0 dsCount=0 flags=0 obj=0x742b2a28 self=0x715052814c00
| sysTid=16842 nice=-10 cgrp=default sched=0/0 handle=0x7150d8012548
| state=R schedstat=( 6802667952 210121864 1103 ) utm=610 stm=70 core=2 HZ=100
| stack=0x7ffefec17000-0x7ffefec19000 stackSize=8MB
| held mutexes= "mutator lock"(shared held)
at com.zero.interviewdemo.MainActivity.clickTest(MainActivity.java:26)
at java.lang.reflect.Method.invoke(Native method)
at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:397)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
可以看到是由于线程死锁导致的anr。
实例2:
可以参考:什么是ANR,如何避免ANR的 trace.txt文件解读
参考:
Iowait的成因、对系统影响及对策
Android 系统中的 WatchDog 详解
性能优化-Android之ANR分析解决 traces.txt文件分析
Android ANR监测方案解析