android ANR发生的原因总结和解决办法
ANR的全称是application not responding,意思就是程序未响应,类似于我们在windows上见到的程序未响应。ANR发生会使用户觉得我们的程序不友好,那么什么情况会导致ANR的发生呢?
首先ANR的发生是有条件限制的,分为以下三点:
1.只有主线程才会产生ANR,主线程就是UI线程;
2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
3.上述事件响应超时,不同的context规定的上限时间不同
- a.主线程对输入事件5秒内没有处理完毕
- b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
- c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
细分的话,导致ANR的原因有如下几点:
- 1.耗时的网络访问
- 2.大量的数据读写
- 3.数据库操作
- 4.硬件操作(比如camera)
- 5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候
- 6.service binder的数量达到上限
- 7.system server中发生WatchDogANR
- 8.service忙导致超时无响应
- 9.其他线程持有锁,导致主线程等待超时
- 10.其它线程终止或崩溃导致主线程一直等待
那么如何避免ANR的发生呢或者说ANR的解决办法是什么呢?
1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。
2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。
3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
1.3 如何解决ANR
- 使用AsyncTask处理耗时IO操作
- 使用Thread(不可以创建handler)或者HandlerThread提高优先级
- 使用Handler来处理工作线程的耗时任务
- Activity的onCreate和onResume回调中尽量少写耗时操作的代码
1.4 ANR问题排查
1 获取trace.txt文件
adb shell cat /data/anr/traces.txt > d:/traces.txt (拷贝到d盘)
2 根据trace.txt分析anr问题的原因
从LOG可以看出ANR的类型,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR
如果CPU使用量很少,说明主线程被BLOCK了
如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的
除了看LOG,解决ANR还得需要trace.txt文件,
看完这篇 Android ANR 分析,就可以和面试官装逼了!
https://juejin.im/entry/5c3e9ef6e51d4539b927dfd1
- ANR的监测机制:首先分析Service和输入事件大致工作流程,然后从Service,InputEvent两种不同的ANR监测机制的源码实现开始,分析了Android如何发现各类ANR。在启动服务、输入事件分发时,植入超时检测,用于发现ANR。
- ANR的报告机制:分析Android如何输出ANR日志。当ANR被发现后,两个很重要的日志输出是:CPU使用情况和进程的函数调用栈,这两类日志是我们解决ANR问题的利器。
- 监测ANR的核心原理是消息调度和超时处理。
- 只有被ANR监测的场景才会有ANR报告以及ANR提示框。
我们先抛出两个问题问题一:Service启动流程?问题一: 如何监测Service超时?
1. Service启动流程如下图所示:
(1)ActiveServices.realStartServiceLocked()在通过app.thread的scheduleCreateService()来创建Service对象并调用Service.onCreate()后,接着又调用sendServiceArgsLocked()方法来调用Service的其他方法,如onStartCommand。以上两步均是进程间通信,应用与AMS之间跨进程通信可以参考应用进程与系统进程通信(2)以上只是列出Service启动流程的关键步骤,具体每个方法主要做哪些工作还需要查看具体的代码,暂时先忽略这些,感兴趣的可以参考Android开发艺术探索等其他相关资料
2. Service超时监测机制Service超时监测机制可以从Service启动流程中找到。
(1)ActiveServices.realStartServiceLocked()主要工作有
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... // 主要是为了设置ANR超时,可以看出在正式启动Service之前开始ANR监测; bumpServiceExecutingLocked(r, execInFg, "create"); // 启动过程调用scheduleCreateService方法,最终会调用Service.onCreate方法; app.thread.scheduleCreateService(r, r.serviceInfo, // 绑定过程中,这个方法中会调用app.thread.scheduleBindService方法 requestServiceBindingsLocked(r, execInFg); // 调动Service的其他方法,如onStartCommand,也是IPC通讯 sendServiceArgsLocked(r, execInFg, true); }
(2)bumpServiceExecutingLocked()会调用scheduleServiceTimeoutLocked()方法
void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; // 在serviceDoneExecutingLocked中会remove该SERVICE_TIMEOUT_MSG消息, // 当超时后仍没有remove SERVICE_TIMEOUT_MSG消息,则执行ActiveServices. serviceTimeout()方法; mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); // 前台进程中执行Service,SERVICE_TIMEOUT=20s;后台进程中执行Service,SERVICE_BACKGROUND_TIMEOUT=200s }
(3)如果在指定的时间内还没有serviceDoneExecutingLocked()方法将消息remove掉,就会调用ActiveServices. serviceTimeout()方法
void serviceTimeout(ProcessRecord proc) { ... final long maxTime = now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); ... // 寻找运行超时的Service for (int i=proc.executingServices.size()-1; i>=0; i--) { ServiceRecord sr = proc.executingServices.valueAt(i); if (sr.executingStart < maxTime) { timeout = sr; break; } ... } ... // 判断执行Service超时的进程是否在最近运行进程列表,如果不在,则忽略这个ANR if (timeout != null && mAm.mLruProcesses.contains(proc)) { anrMessage = "executing service " + timeout.shortName; } ... if (anrMessage != null) { // 当存在timeout的service,则执行appNotResponding,报告ANR mAm.appNotResponding(proc, null, null, false, anrMessage); }}
(4)Service onCreate超时监测整体流程如下图
输入事件超时监测
记一次分析解决ANR过程
经过我查看log信息发现
Reason: Input dispatching timed out (Waiting because the touched window has not finished processing the input events that were previously delivered to it.)
Load: 0.9 / 0.57 / 0.68
CPU usage from 2505ms to -3307ms ago:
==94%== 20357/com.richeninfo.cmoa: 94% user+ 0.3% kernel / faults: 1 minor
22% 810/system_server: 17% user + 5.1% kernel / faults: 1061 minor
0.5% 146/debuggerd: 0.2% user + 0.3% kernel / faults: 2717 minor
3.4% 977/com.android.systemui: 3.2% user + 0.1% kernel / faults: 11 minor
1.8% 1310/com.android.phone: 1.5% user + 0.3% kernel
从LOG可以看出ANR的类型,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR
如果CPU使用量很少,说明主线程被BLOCK了
如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的
所以我这里导致ANR的原因应该是CPU不足。
仅仅查看log的信息还不足以帮我们定位到ANR的原因,所以需要去看data/anr/trace文件
at com.richeninfo.cmoa.widget.AutoScrollViewPager.onTouchEvent(AutoScrollViewPager.java:219)
从这些信息中首先看到线程的状态为”main” prio=5 tid=1 SUSPENDED
而经过一位朋友提示线程状态为SUSPENDED
只有在debug的时候会这样,可是我没在debug啊,所以网上查到下面资料图:
可以看到资料说这种状态通常是由于GC或者debug,所以我的情况应该是就GC了,这也验证了前面说的CPU不足的原因。
Reason: Input dispatching timed out(Waiting because the touched window has not finished processing the input events that were previously delivered to it.)
说明这CPU不足导致无法相应下一个input events导致ANR。
那就去看看onTouchEvent里都执行了什么鬼操作。
announcePager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
announcePager.stopAutoScroll();
break;
case MotionEvent.ACTION_MOVE:
announcePager.startAutoScroll();
break;
case MotionEvent.ACTION_UP:
announcePager.startAutoScroll();
break;
default:
break;
}
return false;
}
});
可以看到相应了三个action,那么就去看看startAutoScroll()stopAutoScroll()都写了什么。
public void startAutoScroll(int delayTimeInMills) {
isAutoScroll = true;
sendScrollMessage(delayTimeInMills);
}
/**
* stop auto scroll
*/
public void stopAutoScroll() {
isAutoScroll = false;
handler.removeMessages(SCROLL_WHAT);
}
/**
* set the factor by which the duration of sliding animation will change
*/
public void setScrollDurationFactor(double scrollFactor) {
scroller.setScrollDurationFactor(scrollFactor);
}
private void sendScrollMessage(long delayTimeInMills) {
/** remove messages before, keeps one message is running at most **/
handler.removeMessages(SCROLL_WHAT);
handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
}
看到是handler在发送消息并且每次发送之前都要把前面的消息移除。
结合我操作APP发生ANR的时机,判断问题应该是出现在action_move响应太频繁,导致频繁startAutoScroll();然后方法内部里频繁handler.removeMessages(),这样被remove的消息由于垃圾回收机制频繁引起GC,所以就导致了CPU不足,这样似乎可以验证前面的说法。
问题找到了,那就要解决,这个解决也简单,直接把action_move里的
startAutoScroll()注释掉就OK了,其实这里也不需要在action_move里执行startAutoScroll(),因为action_up里已经执行了startAutoScroll()。
这样ANR就分析解决完毕了。有了这次经验,以后相信自己能比较好应对ANR。。。认真分析trace信息和log日志.