1. 概览
ANR(Application Not Responding),应用程序没有响应,一个简单的定义涵盖了很多Android系统设计理念。
首先,ANR不同于应用程序的范畴SNR(System Not Respoding),SNR反映的问题是系统过程(system_server)失去响应能力ANR在应用程序中明确圈定问题。 SNR由Watchdog机制保证可以具体查阅Watchdog机制和问题分析; ANR由消息处理机制保证,Android在系统层中实现了一套精确的机制ANR,核心原理是新闻调度和加班。
其次,ANR系统层实现了机制的主体。所有与ANR所有相关将通过系统过程(system_server)同时,系统过程设计了不同的加班限制来跟踪消息的处理。 一旦应用程序处理信息不当,超时限制就会起作用。它收集了一些系统状态,如CPU/IO使用情况、过程函数调用栈,并报告用户有过程无响应(ANR对话框)。
然后,ANR问题的本质是性能问题。ANR事实上,该机制对应用程序主线程的限制要求主线程在有限的时间内完成一些最常见的操作(启动服务、广播和输入), 若处理加班,则认为主线程已失去响应其它操作的能力。主线程中的耗时操作,如密集CPU运算、大量IO、复杂的界面布局会降低应用程序的响应能力。
最后,部分ANR问题难以分析,有时由于系统底部的一些影响,新闻调度失败,问题场景难以复制。 这类ANR问题往往需要花很多时间来理解系统的一些行为,超出了ANR机制本身的范畴。
2. ANR机制
分析一些初级的ANR问题只需要简单地理解最终输出的日志,但对于一些系统问题(例如CPU负载过高,过程卡死)ANR,需要整个ANR只有了解机制,才能定位问题的原因。
ANR机制可分为两部分:
- ANR的监测。Android对于不同的ANR类型(Broadcast, Service, InputEvent)都有一套监控机制。
- ANR报告。在监测中ANR以后,需要显示ANR对话框、输出日志(发生)ANR时间过程函数调用栈,CPU使用情况等)。
整个ANR机制的代码也跨越了Android的几个层:
- App层:应用主线程的处理逻辑
- Framework层: ANR机制的核心
- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
- frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
- frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
- frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
- frameworks/base/core/java/android/view/InputChannel
- frameworks/base/services/core/java/com/android/internal/os/ProcessCpuTracker
- Native层:输入事件分配机制。InputEvent类型的ANR
- frameworks/base//services/core/jni/com_android_server_input_InputManagerService.cpp
- frameworks/native/services/inputflinger/InputDispatcher.cpp
下面,我们将深入分析源代码ANR监控和报告过程。
2.1 ANR的监测机制
2.1.1 Service处理超时
Service如果在应用程序的主线程中运行,Service执行时间超过秒,会引起ANR。
当发生Service ANR一般来说,你可以先检查一下Service在生命周期函数中(onCreate(), onStartCommand()等)是否有耗时的操作,如复杂的操作,IO操作等。 如果应用程序的代码逻辑没有发现问题,则需要深入检查当前系统的状态:CPU判断当时发生用情况、系统服务状态等。ANR该过程是否受到系统运行异常的影响。
如何检测Service超时呢?Android它是通过设置定时消息来实现的。定时消息是由AMS处理的消息队列(system_server的ActivityManager线程)。 AMS有Service操作的上下文信息,所以在AMS设置一套超时检测机制是合理的。
Service ANR机制相对简单,实现主体ActiveServices中。 当Service当生命周期开始时,bumpServiceExecutingLocked()会被调用,然后会被调用scheduleServiceTimeoutLocked():
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
... Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; // 通过AMS.MainHandler抛出定时消息 mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg ? (now SERVICE_TIMEOUT) : (now SERVICE_BACKGROUND_TIMEOUT));
}
上述方法通过AMS.MainHandler抛出一个定时消息SERVICE_TIMEOUT_MSG
:
前台进程中执行Service,超时时间是SERVICE_TIMEOUT(20秒) 后台进程中执行Service,超时时间是SERVICE_BACKGROUND_TIMEOUT(200秒) 当Service的生命周期结束时,会调用serviceDoneExecutingLocked()
方法,之前抛出的SERVICE_TIMEOUT_MSG消息在这个方法中会被清除。 如果在超时时间内,SERVICE_TIMEOUT_MSG没有被清除,那么,AMS.MainHandler就会响应这个消息:
case SERVICE_TIMEOUT_MSG: {
// 判断是否在做dexopt操作, 该操作的比较耗时,允许再延长20秒
if (mDidDexOpt) {
mDidDexOpt = false;
Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);
nmsg.obj = msg.obj;
mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);
return;
}
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
如果不是在做dexopt操作,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) {
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
}
上述方法会找到当前进程已经超时的Service,经过一些判定后,决定要报告ANR,最终调用AMS.appNotResponding()方法。 走到这一步,ANR机制已经完成了监测报告任务,剩下的任务就是ANR结果的输出,我们称之为ANR的报告机制。 ANR的报告机制是通过AMS.appNotResponding()完成的,Broadcast和InputEvent类型的ANR最终也都会调用这个方法,我们后文再详细展开。
至此,我们分析了Service的ANR机制:
通过定时消息跟踪Service的运行,当定时消息被响应时,说明Service还没有运行完成,这就意味着Service ANR。
2.1.2 Broadcast处理超时
应用程序可以注册广播接收器,实现BroadcastReceiver.onReceive()
方法来完成对广播的处理。 通常,这个方法是在主线程执行的,Android限定它执行时间不能超过秒,否则,就会引发ANR。
onReceive()
也可以调度在其他线程执行,通过Context.registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
这个方法注册广播接收器, 可以指定一个处理的Handler,将onReceive()调度在非主线程执行。
这里先把问题抛出来了:
- Android如何将广播投递给各个应用程序?
- Android如何检测广播处理超时?
广播消息的调度 AMS维护了两个广播队列BroadcastQueue:
- foreground queue,前台队列的超时时间是秒
- background queue,后台队列的超时时间是秒
之所以有两个,就是因为要区分的不同超时时间。所有发送的广播都会进入到队列中等待调度,在发送广播时,可以通过Intent.FLAG_RECEIVER_FOREGROUND
参数将广播投递到前台队列。 AMS线程会不断地从队列中取出广播消息派发到各个接收器(BroadcastReceiver)。当要派发广播时,AMS会调用BroadcastQueue.scheduleBroadcastsLocked()
方法:
public void scheduleBroadcastsLocked() {
...
if (mBroadcastsScheduled) {
return;
}
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
mBroadcastsScheduled = true;
}
上述方法中,往AMS线程的消息队列发送BROADCAST_INTENT_MSG
消息,由此也可以看到真正派发广播的是AMS线程(system_server进程中的ActivityManager线程)。 由于上述方法可能被并发调用,所以通过mBroadcastsScheduled这个变量来标识BROADCAST_INTENT_MSG是不是已经被AMS线程接收了,当已经抛出的消息还未被接受时,不需要重新抛出。 该消息被接收后的处理逻辑如下:
public void handleMessage(Message msg) {
switch (msg.what) {
case BROADCAST_INTENT_MSG: {
...
processNextBroadcast(true);
} break;
...
}
}
直接调用BroadcastQueue.processNextBroadcast()
方法,fromMsg参数为true表示这是一次来自BROADCAST_INTENT_MSG消息的派发请求。 BroadcastQueue.processNextBroadcast()是派发广播消息最为核心的函数,代码量自然也不小,我们分成几个部分来分析:
// processNextBroadcast部分1:处理非串行广播消息
final void processNextBroadcast(boolean fromMsg) {
...
// 1. 设置mBroadcastsScheduled
if (fromMsg) {
mBroadcastsScheduled = false;
}
// 2. 处理“并行广播消息”
while (mParallelBroadcasts.size() > 0) {
...
final int N = r.receivers.size();
for (int i=0; i<N; i++) {
Object target = r.receivers.get(i);
deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
}
addBroadcastToHistoryLocked(r);
}
// 3. 处理阻塞的广播消息
if (mPendingBroadcast != null) {
...
if (!isDead) {
// isDead表示当前广播消息的进程的存活状态
// 如果还活着,则返回该函数,继续等待下次派发
return;
}
...
}
//未完待续
第一个部分是处理非”串行广播消息“,有以下几个步骤:
①设置mBroadcastsScheduled
。该变量在前文说过,是对BROADCAST_INTENT_MSG
进行控制。 如果是响应BROADCAST_INTENT_MSG
的派发调用,则将mBroadcastsScheduled
设为false, 表示本次BROADCAST_INTENT_MSG
已经处理完毕,可以继续抛出下一次BROADCAST_INTENT_MSG
消息了
②处理“并行广播消息”。广播接受器有“动态”和“静态”之分,通过Context.registerReceiver()
注册的广播接收器为“动态”的,通过AndroidManifest.xml
注册的广播接收器为“静态”的。 广播消息有“并行”和“串行”之分,“并行广播消息”都会派发到“动态”接收器,“串行广播消息”则会根据实际情况派发到两种接收器。 我们先不去探究Android为什么这么设计,只关注这两种广播消息派发的区别。在BroadcastQueue维护着两个队列:
mParallelBroadcasts
,“并行广播消息”都会进入到此队列中排队。“并行广播消息”可以一次性派发完毕,即在一个循环中将广播派发到所有的“动态”接收器mOrderedBroadcasts
,“串行广播消息”都会进入到此队列中排队。“串行广播消息”需要轮侯派发,当一个接收器处理完毕后,会再抛出BROADCAST_INTENT_MSG
消息, 再次进入BroadcastQueue.processNextBroadcast()
处理下一个
③处理阻塞的广播消息。有时候会存在一个广播消息派发不出去的情况,这个广播消息会保存在mPendingBroadcast
变量中。新一轮的派发启动时,会判断接收该消息的进程是否还活着, 如果接收进程还活着,那么就继续等待。否则,就放弃这个广播消息
接下来是最为复杂的一部分,处理“串行广播消息”,ANR监测机制只在这一类广播消息中才发挥作用,也就是说“并行广播消息”是不会发生ANR的。
// processNextBroadcast部分2:从队列中取出“串行广播消息”
do {
r = mOrderedBroadcasts.get(0);
// 1. 广播消息的第一个ANR监测机制
if (mService.mProcessesReady && r.dispatchTime > 0) {
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
broadcastTimeoutLocked(false); // forcibly finish this broadcast
...
}
// 2. 判断该广播消息是否处理完毕
if (r.receivers == null || r.nextReceiver >= numReceivers ||
r.resultAbort || forceReceive) {
...
cancelBroadcastTimeoutLocked();
...
mOrderedBroadcasts.remove(0);
continue;
}
} while (r == null);
//未完待续
这部分是一个do-while循环,每次都从mOrderedBroadcasts
队列中取出第一条广播消息进行处理。第一个Broadcast ANR监测机制千呼万唤总算是出现了:
①判定当前时间是否已经超过了r.dispatchTime + 2×mTimeoutPeriod×numReceivers
:
dispatchTime
表示这一系列广播消息开始派发的时间。“串行广播消息”是逐个接收器派发的,一个接收器处理完毕后,才开始处理下一个消息派发。 开始派发到第一个接收器的时间就是dispatchTime
。dispatchTime
需要开始等广播消息派发以后才会设定,也就是说,第一次进入processNextBroadcast()
时,dispatchTime=0
,并不会进入该条件判断mTimeoutPeriod
由当前BroadcastQueue的类型决定(forground为10秒,background为60秒)。这个时间在初始化BroadcastQueue的时候就设置好了, 本意是限定每一个Receiver处理广播的时间,这里利用它做了一个超时计算
假设一个广播消息有2个接受器,mTimeoutPeriod
是10秒,当2×10×2=40秒后,该广播消息还未处理完毕,就调用broadcastTimeoutLocked()
方法, 这个方法会判断当前是不是发生了ANR,我们后文再分析。
②如果广播消息是否已经处理完毕,则从mOrderedBroadcasts中移除,重新循环,处理下一条;否则,就会跳出循环。
以上代码块完成的主要任务是从队列中取一条“串行广播消息”,接下来就准备派发了:
// processNextBroadcast部分3:串行广播消息的第二个ANR监测机制
r.receiverTime = SystemClock.uptimeMillis();
...
if (! mPendingBroadcastTimeoutMessage) {
long timeoutTime = r.receiverTime + mTimeoutPeriod;
...
setBroadcastTimeoutLocked(timeoutTime);
}
//未完待续
取出“串行广播消息”后,一旦要开始派发,第二个ANR检测机制就出现了。mPendingBroadcastTimeoutMessage
变量用于标识当前是否有阻塞的超时消息, 如果没有则调用BroadcastQueue.setBroadcastTimeoutLocked():
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
通过设置一个定时消息BROADCAST_TIMEOUT_MSG
来跟踪当前广播消息的执行情况,这种超时监测机制跟Service ANR很类似,也是抛到AMS线程的消息队列。 如果所有的接收器都处理完毕了,则会调用cancelBroadcastTimeoutLocked()
清除该消息;否则,该消息就会响应,并调用broadcastTimeoutLocked()
, 这个方法在第一种ANR监测机制的时候调用过,第二种ANR监测机制也会调用,我们留到后文分析。
设置完定时消息后,就开始派发广播消息了,首先是“动态”接收器:
// processNextBroadcast部分4: 向“动态”接收器派发广播消息
final Object nextReceiver = r.receivers.get(recIdx);
// 动态接收器的类型都是BroadcastFilter
if (nextReceiver instanceof BroadcastFilter) {
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
deliverToRegisteredReceiverLocked(r, filter, r.ordered);
...
return;
}
//未完待续
“动态”接收器的载体进程一般是处于运行状态的,所以向这种类型的接收器派发消息相对简单,调用BroadcastQueue.deliverToRegisteredReceiverLocked()
完成接下来的工作。 但“静态”接收器是在AndroidManifest.xml
中注册的,派发的时候,可能广播接收器的载体进程还没有启动,所以,这种场景会复杂很多。
// processNextBroadcast部分5: 向“静态”接收器派发广播消息
// 静态接收器的类型都是 ResolveInfo
ResolveInfo info = (ResolveInfo)nextReceiver;
...
// 1. 权限检查
ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
...
// 2. 获取接收器所在的进程
ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
info.activityInfo.applicationInfo.uid, false);
// 3. 进程已经启动
if (app != null && app.thread != null) {
...
processCurBroadcastLocked(r, app);
return;
}
// 4. 进程还未启动
if ((r.curApp=mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
"broadcast", r.curComponent,
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
== null) {
...
scheduleBroadcastsLocked();
return;
}
// 5. 进程启动失败
mPendingBroadcast = r;
mPendingBroadcastRecvIndex = recIdx;
}
// processNextBroadcast完
-
“静态”接收器是
ResolveInfo
,需要通过PackageManager
获取包信息,进行权限检查。权限检查的内容非常庞大,此处不表。 -
经过一系列复杂的权限检查后,终于可以向目标接收器派发了。通过
AMS.getProcessRecordLocked()
获取广播接收器的进程信息 -
如果
app.thread != null
,则进程已经启动,就可以调用BroadcastQueue.processCurBroadcastLocked()
进行接下来的派发处理了 -
如果进程还没有启动,则需要通过
AMS.startProcessLocked()
来启动进程,当前消息并未派发,调用BroadcastQueue.scheduleBroadcastsLocked()
进入下一次的调度 -
如果进程启动失败了,则当前消息记录成
mPendingBroadcast
,即阻塞的广播消息,等待下一次调度时处理
庞大的processNextBroadcast()
终于完结了,它的功能就是对广播消息进行调度,该方法被设计得十分复杂而精巧,用于应对不同的广播消息和接收器的处理。
广播消息的跨进程传递 调度是完成了,接下来,我们就来分析被调度广播消息如何到达应用程序。上文的分析中,最终有两个方法将广播消息派发出去:BroadcastQueue.deliverToRegisteredReceiverLocked()
和BroadcastQueue.processCurBroadcastLocked()
。
我们先不展开这两个函数的逻辑,试想要将广播消息的从AMS线程所在的system_server进程传递到应用程序的进程,该怎么实现? 自然需要用到跨进程调用,Android中最常规的手段就是Binder机制。没错,广播消息派发到应用进程就是这么玩的。
对于应用程序已经启动(app.thread != null
)的情况,会通过IApplicationThread
发起跨进程调用, 调用关系如下:
ActivityThread.ApplicationThread.scheduleReceiver()
└── ActivityThread.handleReceiver()
└── BroadcastReceiver.onReceive()
对于应用程序还未启动的情况,会调用IIntentReceiver
发起跨进程调用,应用进程的实现在LoadedApk.ReceiverDispatcher.IntentReceiver
中, 调用关系如下:
LoadedApk.ReceiverDispatcher.IntentReceiver.performReceive()
└── LoadedApk.ReceiverDispatcher.performReceiver()
└── LoadedApk.ReceiverDispatcher.Args.run()
└── BroadcastReceiver.onReceive()
最终,都会调用到BroadcastReceiver.onReceive()
,在应用进程执行接收广播消息的具体动作。 对于“串行广播消息”而言,执行完了以后,还需要通知system_server
进程,才能继续将广播消息派发到下一个接收器,这又需要跨进程调用了。 应用进程在处理完广播消息后,即在BroadcastReceiver.onReceive()
执行完毕后,会调用BroadcastReceiver.PendingResult.finish()
, 接下来的调用关系如下:
BroadcastReceiver.PendingResult.finish()
└── BroadcastReceiver.PendingResult.sendFinished()
└── IActivityManager.finishReceiver()
└── ActivityManagerService.finishReceiver()
└── BroadcastQueue.processNextBroadcat()
通过IActivityManager
发起了一个从应用进程到system_server
进程的调用,最终在AMS线程中,又走到了BroadcastQueue.processNextBroadcat()
, 开始下一轮的调度。
broadcastTimeoutLocked()方法 前文说过,两种ANR机制最终都会调用BroadcastQueue.broadcastTimeoutLocked()
方法, 第一种ANR监测生效时,会将fromMsg设置为false;第二种ANR监测生效时,会将fromMsg参数为True时,表示当前正在响应BROADCAST_TIMEOUT_MSG
消息。
final void broadcastTimeoutLocked(boolean fromMsg) {
// 1. 设置mPendingBroadcastTimeoutMessage
if (fromMsg) {
mPendingBroadcastTimeoutMessage = false;
}
...
// 2. 判断第二种ANR机制是否超时
BroadcastRecord r = mOrderedBroadcasts.get(0);
if (fromMsg) {
long timeoutTime = r.receiverTime + mTimeoutPeriod;
if (timeoutTime > now) {
setBroadcastTimeoutLocked(timeoutTime);
return;
}
}
...
// 3. 已经超时,则结束对当前接收器,开始新一轮调度
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
// 4. 抛出绘制ANR对话框的消息
if (anrMessage != null) {
mHandler.post(new AppNotResponding(app, anrMessage));
}
}
-
mPendingBroadcastTimeoutMessage
标识是否存在未处理的BROADCAST_TIMEOUT_MSG
消息, 将其设置成false,允许继续抛出BROADCAST_TIMEOUT_MSG
消息 -
每次将广播派发到接收器,都会将
r.receiverTime
更新,如果判断当前还未超时,则又抛出一个BROADCAST_TIMEOUT_MSG
消息。 正常情况下,所有接收器处理完毕后,才会清除BROADCAST_TIMEOUT_MSG
;否则,每进行一次广播消息的调度,都会抛出BROADCAST_TIMEOUT_MSG
消息 -
判断已经超时了,说明当前的广播接收器还未处理完毕,则结束掉当前的接收器,开始新一轮广播调度
-
最终,发出绘制ANR对话框的消息
至此,我们回答了前文提出的两个问题:
2.1.3 Input处理超时
应用程序可以接收输入事件(按键、触屏、轨迹球等),当5秒内没有处理完毕时,则会引发ANR。
如果Broadcast ANR一样,我们抛出Input ANR的几个问题:
- 输入事件经历了一些什么工序才能被派发到应用的界面?
- 如何检测到输入时间处理超时?
输入事件最开始由硬件设备(譬如按键或触摸屏幕)发起,Android有一套输入子系统来发现各种输入事件, 这些事件最终都会被InputDispatcher分发到各个需要接收事件的窗口。 那么,窗口如何告之InputDispatcher自己需要处理输入事件呢?Android通过InputChannel 连接InputDispatcher和窗口,InputChannel其实是封装后的Linux管道(Pipe)。 每一个窗口都会有一个独立的InputChannel,窗口需要将这个InputChannel注册到InputDispatcher中:
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
...
sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
int fd = inputChannel->getFd();
mConnectionsByFd.add(fd, connection);
...
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
...
mLooper->wake();
return OK;
}
对于InputDispatcher
而言,每注册一个InputChannel
都被视为一个Connection
,通过文件描述符来区别。InputDispatcher
是一个消息处理循环,当有新的Connection
时,就需要唤醒消息循环队列进行处理。
输入事件的类型有很多,按键、轨迹球、触屏等,Android对这些事件进行了分类,处理这些事件的窗口也被赋予了一个类型(targetType
):Foucused或Touched, 如果当前输入事件是按键类型,则寻找Focused类型的窗口;如