[TOC]
## 简介
ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多Android系统的设计思想。
ANR由消息处理机制保证,Android在系统层实现了一套精密的机制来发现ANR,核心原理是 **消息调度** 和 **超时处理** 。
造成ANR的场景有
* Service Timeout:比如前台服务在20s内未执行完成;
* BroadcastQueue Timeout:比如前台广播在10s内未执行完成
* ContentProvider Timeout:内容提供者,在publish过超时10s;
* InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
## ANR监测机制
ANR监测机制包含三种:
* **Service ANR**,前台进程中Service生命周期不能超过20秒,后台进程中Service的生命周期不能超过200秒。 在启动Service时,抛出定时消息SERVICE\_TIMEOUT\_MSG或SERVICE\_BACKGOURND\_TIMEOUT\_MSG,如果定时消息响应了,则说明发生了ANR
* **Broadcast ANR**,前台的“串行广播消息”必须在10秒内处理完毕,后台的“串行广播消息”必须在60秒处理完毕, 每派发串行广播消息到一个接收器时,都会抛出一个定时消息BROADCAST\_TIMEOUT\_MSG,如果定时消息响应,则判断是否广播消息处理超时,超时就说明发生了ANR
* **Input ANR**,输入事件必须在5秒内处理完毕。在派发一个输入事件时,会判断当前输入事件是否需要等待,如果需要等待,则判断是否等待已经超时,超时就说明发生了ANR
## Service处理超时源码介绍
Service运行在应用程序的主线程,如果Service的执行时间超过20秒,则会引发ANR。
当发生Service ANR时,一般可以先排查一下在Service的生命周期函数中(onCreate(), onStartCommand()等)有没有做耗时的操作,譬如复杂的运算、IO操作等。
如何检测Service超时呢?Android是通过设置定时消息实现的。定时消息是由AMS的消息队列处理的(system\_server的ActivityManager线程)。 AMS有Service运行的上下文信息,所以在AMS中设置一套超时检测机制也是合情合理的。
Service ANR机制相对最为简单,主体实现在[ActiveServices.java,**realStartServiceLocked** 中。 当Service的生命周期开始时,**bumpServiceExecutingLocked** 会被调用,紧接着会调**scheduleServiceTimeoutLocked**ActiveServices.java
### 埋炸弹
ActiveServices(mAm.mHandler )
~~~
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
...
//发送delay消息(SERVICE_TIMEOUT_MSG),【见小节2.1.2】
bumpServiceExecutingLocked(r, execInFg, "create");
try {
...
//最终执行服务的onCreate()方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {
...
}
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
...
scheduleServiceTimeoutLocked(r.app);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
long now = SystemClock.uptimeMillis();
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程【见2.3.1】
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
~~~
### 拆炸弹
ActivityThread.java
~~~
private void handleCreateService(CreateServiceData data) {
...
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
...
try {
//创建ContextImpl对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//创建Application对象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//调用服务onCreate()方法
service.onCreate();
//拆除炸弹引线[见小节2.2.2]
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}
~~~
### 引爆炸弹
~~~
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case SERVICE_TIMEOUT_MSG: {
...
//【见小节2.3.2】
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
...
}
...
}
}
~~~
serviceTimeout
~~~
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;
}
}
if (anrMessage != null) {
//当存在timeout的service,则执行appNotResponding
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
~~~
## Input处理超时
![](https://img.kancloud.cn/a6/9f/a69fd2570e083d83ac74e55bfd11411f_863x231.png)
内核将原始事件写入到设备节点中,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由窗口事件处理函数进行事件响应。关键流程有:原始输入事件的读取与加工;输入事件的派发;输入事件的发送、接收与反馈。其中输入事件派发是指InputDispatcher不断的从派发队列取出事件、寻找合适的窗口进行发送的过程,输入事件的发送是InputDispatcher通过Connection对象将事件发送给窗口的过程。
InputDispatcher与窗口之间的跨进程通信主要通过InputChannel来完成。在InputDispatcher与窗口通过InputChannel建立连接之后,就可以进行事件的发送、接收与反馈;输入事件的发送和接收主要流程如图所示
InputDispatcher作为中枢,不停地在递送着输入事件,当一个事件无法得到处理的时候,InputDispatcher不能就此死掉啊,否则系统也太容易崩溃了。 **InputDispatcher的策略是放弃掉处理不过来的事件,并发出通知(这个通知机制就是ANR)**,继续进行下一轮消息的处理。
事件分发5s限制定义在InputDispatcher.cppInputDispatcher::handleTargetsNotReadyLocked方法中如果事件5s之内还没有分发完毕,则调用InputDispatcher::onANRLocked()提示用户应用发生ANR;
~~~
//默认分发超时间为5s
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL;
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
// 1.如果当前没有聚焦窗口,也没有聚焦的应用
if (applicationHandle == NULL && windowHandle == NULL) {
...
} else {
// 2.有聚焦窗口或者有聚焦的应用
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
// 获取等待的时间值
if (windowHandle != NULL) {
// 存在聚焦窗口,DEFAULT_INPUT_DISPATCHING_TIMEOUT事件为5s
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != NULL) {
// 存在聚焦应用,则获取聚焦应用的分发超时时间
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
// 默认的分发超时时间为5s
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
}
}
// 如果当前时间大于输入目标等待超时时间,即当超时5s时进入ANR处理流程
// currentTime 就是系统的当前时间,mInputTargetWaitTimeoutTime 是一个全局变量,
if (currentTime >= mInputTargetWaitTimeoutTime) {
// 调用ANR处理流程
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
// 返回需要等待处理
return INPUT_EVENT_INJECTION_PENDING;
}
}
~~~
当应用主线程被卡住的事件,再点击该应用其它组件也是无响应,因为事件派发是串行的,上一个事件不处理完毕,不会处理下一个事件。
Activity.onCreate执行耗时操作,不管用户如何操作都不会发生ANR,因为输入事件相关监听机制还没有建立起来;InputChannel通道还没有建立这时是不会响应输入事件,InputDispatcher还不能事件发送到应用窗口,ANR监听机制也还没有建立,所以此时是不会报告ANR的。
## ANR的报告机制
无论哪种类型的ANR发生以后,最终都会调用 AMS.appNotResponding() 方法,所谓“殊途同归”。这个方法的职能就是向用户或开发者报告ANR发生了。 最终的表现形式是:弹出一个对话框,告诉用户当前某个程序无响应;输入一大堆与ANR相关的日志,便于开发者解决问题。
最终形式我们见过很多,但输出日志的原理是什么,未必所有人都了解,下面我们就来认识一下是如何输出ANR日志
~~~
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
...
if (ActivityManagerService.MONITOR_CPU_USAGE) {
// 1. 更新CPU使用信息。ANR的第一次CPU信息采样,采样数据会保存在mProcessStats这个变量中
mService.updateCpuStatsNow();
}
// 记录ANR到EventLog中
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// 输出ANR到main log.
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
// 3. 打印调用栈。具体实现由dumpStackTraces()函数完成
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
String cpuInfo = null;
// MONITOR_CPU_USAGE默认为true
if (ActivityManagerService.MONITOR_CPU_USAGE) {
// 4. 更新CPU使用信息。ANR的第二次CPU使用信息采样。两次采样的数据分别对应ANR发生前后的CPU使用情况
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
// 输出ANR发生前一段时间内各个进程的CPU使用情况
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
// 输出CPU负载
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
// 输出ANR发生后一段时间内各个进程的CPU使用率
info.append(processCpuTracker.printCurrentState(anrTime));
//会打印发生ANR的原因,如输入事件导致ANR的不同场景
Slog.e(TAG, info.toString());
if (tracesFile == null) {
// There is no trace file, so dump (only) the alleged culprit's threads to the log
// 发送signal 3(SIGNAL_QUIT)来dump栈信息
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
// 将anr信息同时输出到DropBox
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
// Bring up the infamous App Not Responding dialog
// 5. 显示ANR对话框。抛出SHOW_NOT_RESPONDING_MSG消息,
// AMS.MainHandler会处理这条消息,显示AppNotRespondingDialog对话框提示用户发生ANR
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mService.mUiHandler.sendMessage(msg);
}
~~~
该方法的主体逻辑可以分成五个部分来看:
* 更新CPU的统计信息。这是发生ANR时,第一次CPU使用信息的采样,采样数据会保存在mProcessStats这个变量中
* 填充firstPids和lastPids数组。当前发生ANR的应用会首先被添加到firstPids中,这样打印函数栈的时候,当前进程总是在trace文件的最前面
* 打印函数调用栈(StackTrace)。具体实现由dumpStackTraces()函数完成
* 更新CPU的统计信息。这是发生ANR时,第二次CPU使用信息的采样,两次采样的数据分别对应ANR发生前后的CPU使用情况
* 显示ANR对话框。抛出SHOW\_NOT\_RESPONDING\_MSG消息,AMS.MainHandler会处理这条消息,显示AppNotRespondingDialog
当然,除了主体逻辑,发生ANR时还会输出各种类别的日志:
* event log,通过检索”am\_anr”关键字,可以找到发生ANR的应用
* main log,通过检索”ANR in “关键字,可以找到ANR的信息,日志的上下文会包含CPU的使用情况
* dropbox,通过检索”anr”类型,可以找到ANR的信息
* traces, 发生ANR时,各进程的函数调用栈信息
我们分析ANR问题,往往是从main log中的CPU使用情况和traces中的函数调用栈开始。所以,更新CPU的使用信息updateCpuStatsNow()方法和打印函数栈dumpStackTraces()方法,是系统报告ANR问题关键所在。
## 参考资料
[ANR机制以及问题分析](https://duanqz.github.io/2015-10-12-ANR-Analysis)
[理解Android ANR的触发原理](http://gityuan.com/2016/07/02/android-anr/)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台