源码剖析Android ANR产生机制

源码剖析Android ANR产生机制

如下采用Android源码的android-11.0.0_r48分支进行,不同版本源码差异巨大。
ANR的捕获起点为ProcessRecord.appNotResponding方法,本文由appNotResponding逆推ANR的产生机制。
源码:frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java。

通过源码搜索appNotResponding,发现系统提供了AnrHelper类,封装了ProcessRecord.appNotResponding,所有ANR产生后,调用都会走到这里。通过搜索发现Activity、Broadcast、Service、ContentProvider都会调用AnrHelper.appNotResponding,也就是说Android四大组件都有可能产生ANR。
源码:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java

Activity ANR

在开始Activity ANR之前,先问个问题:
创建一个Android Hello World工程,添加一个Button,在Button的onClick中回调SystemClock.sleep(10 * 1000)。

  • 运行App,点击一次按钮,发现按钮处于Pressed状态,因为阻塞了UI线程,那么一直等待,会不会产生ANR呢?结果是不会,10秒后,会看到Button状态恢复正常,并没有ANR框弹出,也没有产生ANR。
  • 运行App,连续点击两次按钮,一直等待会不会产生ANR呢?结果是会。

下面开始源码分析:

  1. 从AnrHelper.appNotResponding出发,搜索调用地方。发现在ActivityManagerService中有inputDispatchingTimedOut方法,最终调用到AnrHelper.appNotResponding,最终走到ANR流程。此处通过函数名基本得到是输入超时导致的ANR。
    源码:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  2. 通过查找inputDispatchingTimedOut调用,最终发现ActivityRecord、AnrController两个类中有调用
    源码:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
    源码:frameworks/base/services/core/java/com/android/server/wm/AnrController.java
    此处通过路径发现,Activity过来的ANR的触发点在WindowManager中,也就是Activity中的Window才会触发ANR。
    继续看源码ActivityRecord中的inputDispatchingTimedOut,发现其只有转调WindowManager.inputDispatchingTimedOut,并没有触发逻辑。
    继续看源码AnrController中的inputDispatchingTimedOut,发现其中提供了几种类型的ANR函数供调用:notifyAppUnresponsive、notifyWindowUnresponsive、notifyGestureMonitorUnresponsive,如下逐个分析。

notifyAppUnresponsive调用栈:

frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyNoFocusedWindowAnr)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyNoFocusedWindowAnr)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyNoFocusedWindowAnrLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(std::shared_ptr<InputApplicationHandle> application))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processNoFocusedWindowAnrLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)

notifyWindowUnresponsive调用栈:

frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyWindowUnresponsive)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyWindowUnresponsive)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::sendWindowUnresponsiveCommandLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processConnectionUnresponsiveLocked) 
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(const sp<Connection>& connection))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)

notifyGestureMonitorUnresponsive调用栈:

frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyGestureMonitorUnresponsive)
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java (notifyMonitorUnresponsive)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyMonitorUnresponsive)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::sendMonitorUnresponsiveCommandLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processConnectionUnresponsiveLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(const sp<Connection>& connection))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)

从调用栈可见,如上3种类型的ANR最终都会收敛至InputDispatcher::dispatchOnce函数,那么接下来先看此函数。
InputDispatcher::dispatchOnce为InputDispatcher::start函数中开启的名为InputDispatcher线程的运行体,InputDispatcher线程启动后,如果没有停止,则一直循环调用InputDispatcher::dispatchOnce,此函数最后的mLooper->pollOnce(timeoutMillis)将线程Block住,等待timeoutMillis时间,然后开启下一轮InputDispatcher::dispatchOnce调用(mLooper采用Linux epoll机制进行block,等待timeoutMillis的调用为epoll_wait(…, timeoutMillis)),timeoutMillis的值为5000,也即每5秒。
在某些事件发生时(如:输入事件),可以调用mLooper->wake(),停掉等待,立刻调用InputDispatcher::dispatchOnce,然后其中会调用processAnrsLocked判断此次是否发生ANR。

接下来看InputDispatcher::processAnrsLocked,此函数判断是否产生ANR,函数的注释简单来说就是“等待的事件时长超过Window timeout(5秒),就产生ANR”,接下来通过源码再行验证一下。

  • 其中首先判断if (currentTime >= *mNoFocusedWindowTimeoutTime),为true就调用processNoFocusedWindowAnrLocked()产生ANR。
  • 如果上述判断没通过,意味着具备了Focused Window,那么则继续判断操作是否超时,如果if (currentTime < nextAnrCheck)判断没通过,不幸超时,则走到InputDispatcher::onAnrLocked(const sp& connection),产生connection类型的ANR,即:Activity Window类型的ANR,下面会详解。

再回到最开始的InputDispatcher::dispatchOnce函数,除了轮询调用外,在某些事件发生时(调用mLooper->wake()停掉等待),也会被调用。那么也就意味着每个促使mLooper->wake()被调用的事件,都将可能产生ANR。
源码:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
在源码中搜索mLooper->wake(),发现有22个匹配,源码中几乎每个调用都有注释,如下列举几个常见的事件:

  • InputDispatcher::notifyConfigurationChanged – 配置改变,如:横竖屏切换
  • InputDispatcher::notifyKey – 发生输入事件
  • InputDispatcher::notifySensor – 传感器信息改变
  • InputDispatcher::setInputWindows – 设置输入窗口
  • InputDispatcher::setFocusedApplication – 为App设置焦点
  • InputDispatcher::transferTouchFocus – 传递触摸焦点
  • InputDispatcher::setFocusedWindow – 设置窗口焦点
  • InputDispatcher::displayRemoved – 移除Display

回到上面3个ANR函数调用(notifyAppUnresponsive、notifyWindowUnresponsive、notifyGestureMonitorUnresponsive),这3个函数分别代表3种类型的ANR。

  • notifyAppUnresponsive:No Window Focus Timeout。指Activity还没有Focused Window(包括No Attach Window),并且上述22种事件到来,长达5秒钟无响应,从而导致ANR (InputDispatcher::processNoFocusedWindowAnrLocked)
  • notifyWindowUnresponsive:Window Timeout。指Activity中的Window在上述22种事件到来时,长达5秒钟无响应,从而导致ANR (InputDispatcher::processConnectionUnresponsiveLocked)
  • notifyGestureMonitorUnresponsive:Window Monitor Timeout。跟notifyWindowUnresponsive类似,不过此处是注册了Window事件的Monitor,如:PointerEventDispatcher (InputDispatcher::processConnectionUnresponsiveLocked)
  1. 无论UI阻塞多长时间,只要没有必要事件需要Dispatch(mLooper->wake()),则不会产生ANR
  2. 如果在InputDispatcher::dispatchOnce的时刻,恰好Input事件没有超时5秒,则也不会产生ANR

注:经测试,华为/小米厂商将ANR的5000ms改为了8000ms。可以通过adb shell dumpsys input | grep dispatchingTimeout来获取系统值。

adb logcat -s ActivityManager可以查看ANR的初步信息,如:

E ActivityManager: ANR in com.huya.mtp.myanr (com.huya.mtp.myanr/.MainActivity)
E ActivityManager: PID: 10789
E ActivityManager: Reason: Input dispatching timed out (f69705 com.huya.mtp.myanr/com.huya.mtp.myanr.MainActivity (server) is not responding. Waited 5005ms for MotionEvent(deviceId=9, source=0x00005002, displayId=0, action=DOWN, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=30.3, yPrecision=17.1, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (405.0, 269.9)]), policyFlags=0x62000000)
E ActivityManager: Parent: com.huya.mtp.myanr/.MainActivity

总结:Activity有3种ANR类型,分别为:No Window Focus Timeout,Window Timeout,Window Monitor Timeout。而这3种类型具备22个触发事件点,函数调用为:mLooper->wake()。Android官方设置的超时时间为5000ms。

Broadcast ANR

在开始Broadcast ANR之前,同样问个问题:
创建一个Android Hello World工程,动态注册一个BroadcastReceiver,在onReceiver中调用SystemClock.sleep(100 * 1000),添加一个Button,点击后发送广播让动态注册的BroadcastReceiver接收。

  • 运行App,点击一次按钮,发现有可能按钮处于Pressed状态,因为阻塞了UI线程,那么一直等待,会不会产生ANR呢?结果是不会
  • 如果将动态注册改为静态注册,再次运行,会不会产生ANR呢?结果是会

下面开始源码分析:

1. 继续来从AnrHelper.appNotResponding出发,搜索调用地方。发现在BroadcastQueue中有broadcastTimeoutLocked方法,最终调用到AnrHelper.appNotResponding,最终走到ANR流程。此处通过函数名基本得到是广播超时导致的ANR。

源码:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

2. 查看broadcastTimeoutLocked源码发现,其从一个变量名为mDispatcher的对象中取活跃的广播,然后对这个广播进行发送。mDispatcher的类型为BroadcastDispatcher,根据类注释知道,这个类是用来管理有序广播的。有序广播记录在BroadcastDispatcher.mOrderedBroadcasts的ArrayList中。

源码:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue/BroadcastDispatcher.java

3. 回到BroadcastQueue中,发现除了存储有序广播的BroadcastDispatcher mDispatcher对象外,还存在并行广播类型ArrayList mParallelBroadcasts。这两种类型的广播在都会在processNextBroadcastLocked方法中被处理,processNextBroadcastLocked方法代码700多行,直接反推比较绕,下面改变方向,采用顺推方式。

源码:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

4. 现在回到Android四大组件的Broadcast,发送广播有如下3种方式:

  • Context.sendBroadcast:发送普通广播(并行广播)
  • Context.sendOrderedBroadcast:发送有序广播(串行广播)
  • Context.sendStickyBroadcast:发送粘性广播
    由于粘性广播已被废弃,所以接下来只看普通广播与有序广播。广播的并行与串行是针对接收方来说的。

5. 下面进入源码调用链

Context.sendBroadcast --> ContextWrapper.sendBroadcast --> ContextImpl.sendBroadcast --> ActivityManagerService.broadcastIntentWithFeature
Context.sendOrderedBroadcast --> ContextWrapper.sendOrderedBroadcast --> ContextImpl.sendOrderedBroadcast --> ActivityManagerService.broadcastIntentWithFeature

调用最终通过Binder跨进程走到了ActivityManagerService.broadcastIntentWithFeature,通过参数设置了是否有序,参数为broadcastIntentWithFeature方法中的倒数第3个参数(boolean serialized),broadcastIntentWithFeature方法有15个参数。

6. 继续深入ActivityManagerService.broadcastIntentWithFeature查看调用链

ActivityManagerService.broadcastIntentWithFeature ----> ActivityManagerService.broadcastIntentLocked

经过重载调用来到了ActivityManagerService.broadcastIntentLocked方法,这又是一个700行左右的大方法,看来接下来只能死磕了。此处核心关注点在于广播如何被保存起来。
6.1. 函数最开始判断是否为Instant状态、判断广播白名单、判断系统是否完成启动、判断UserId等,这些跟并行与串行广播没啥关系,跳过
6.2. 判断bOptions是否为null(注意:不是变量brOptions),bOptions是给System发送广播用的,App发送的广播bOptions写死为null,所以跳过bOptions判断的整段代码。
6.3. 判断是否为Protected Broadcast,根据注释,这仍然是System行为,跳过
6.4. 接下来对发送者与系统广播做一次安全检测,不符合则抛出SecurityException,继续跟并行与串行没啥关系,跳过
6.5. 如果广播的action不为null,则进入一个巨大的switch case,用于处理Intent.ACTION_PACKAGE_REMOVED等预定义广播,跟我们自定义的没关系,继续跳过
6.6. 接下来处理粘性广播(sticky),并为之添加一些属性。由于粘性不在本文范围内,并且也已经废弃,跳过
6.7. 接下来获取广播的users,跳过
6.8. 接下来看到注释“// Figure out who all will receive this broadcast”,来了来了,读完500行代码后,终于来到广播接收的处理逻辑处了。
6.9. 首先判断Intent是否不具备Intent.FLAG_RECEIVER_REGISTERED_ONLY属性,如果不具备则调用collectReceiverComponents收集receivers。根据Android文档Intent.FLAG_RECEIVER_REGISTERED_ONLY表示动态注册的广播类型,不会Launch BroadcastReceiver components。
6.9.1. 接下去开启支线任务,进到collectReceiverComponents查看收集到了一些什么receivers,方法返回类型为List,ResolveInfo即为IntentFilter中的Intent。
6.9.2. collectReceiverComponents的主要是调用了AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, pmFlags, user).getList()来收集List
6.9.2.1. 接下来继续开启支线任务,进入到queryIntentReceivers看看怎么样将List收集到的。

PackageManagerService.queryIntentReceivers --> PackageManagerService.queryIntentReceiversInternal --> ComponentResolver.queryReceivers --> ComponentResolver.queryIntent

ComponentResolver类用于解析Android四大组件,最终的调用ComponentResolver.queryIntent将所有的静态与动态注册广播的Intent的query出来,并调用PackageManagerService.applyPostResolutionFilter解析后保存到List中,最终返回给collectReceiverComponents(这个支线扯远了,已经到PackageManagerService中了)。
6.10. 回到ActivityManagerService,此时receivers已经赋值,其中的内容为所有通过Resolve的Intent的List,也就是所有静态与动态注册的广播接收者List。
6.11. 接下来调用mReceiverResolver.queryIntent为registeredReceivers赋值。好家伙,又来一个广播接收者List,此处支线下去看的话,发现返回的仅为动态注册的接收者List(支线任务就不展开讲了,感兴趣可以自己阅读源码)。
6.12. 接下来采用白名单对registeredReceivers进行处理,跟并行与串行无关,跳过。
6.13. 此处总结一下,上面分析了那么多,最终得到了两个广播接收者List:

  • receivers:所有注册的广播接收者,包括静态与动态
  • registeredReceivers:仅为动态注册的广播接收者

6.14. 接下来判断:如果不是串行广播,并且具备接收者(registeredReceivers.size() > 0),则调用queue.enqueueParallelBroadcastLocked发送广播。从方法名可以看出来,此处是并行的,enqueueParallelBroadcastLocked将广播添加到mParallelBroadcasts,挥应了上面逆推中的第3点,而mParallelBroadcasts中的广播怎么被处理,后面再讲。
6.15. 接下来判断是否存在receivers,如果存在,则进行安全检测,避免监听Intent.ACTION_PACKAGE_ADDED做App立即启动的后门,此处与我们主题无关,跳过。
6.16. 接下来按照广播的优先级priority进行排序,串行的广播按照优先级发送。
6.17. 接下来做一些List操作后,最终调用queue.enqueueOrderedBroadcastLocked发送广播。同样从方法名可以看出,此处是串行的,enqueueOrderedBroadcastLocked将广播添加到mDispatcher中,挥应了上面逆推中的第2点,至于广播怎么被处理,同样后面再讲。
6.18. 看完这个700行的大方法后,最后总结一下:

  • mParallelBroadcasts:并行广播发送队列,其中要求广播接收者必须是动态注册的,并且采用非Order的sendBroadcast发送。
  • mDispatcher:串行广播发送队列管理类,如果广播接收者是静态注册的或者采用Order的sendOrderedBroadcast发送。
    也就是说:
  • 动态注册广播接收者的广播,采用sendBroadcast发送则是并行发送与接受的。
  • 静态注册广播接收者的广播,无论怎样发送都是串行的接受的。
  • 采用sendOrderedBroadcast发送的广播,无论接收者是静态还是动态注册的,都将串行接收。

7. 上面完成串行并行广播的入列之后,接下去看一下队列中的广播是怎样触发消费的(也就是说了解了生产者逻辑后,看看如何消费的,并优先看看如何触发消费的)。

7.1. 在queue.enqueueParallelBroadcastLocked与queue.enqueueOrderedBroadcastLocked方法后,都能看到queue.scheduleBroadcastsLocked调用,从函数名看出这个应该是调度广播去消费了。
7.2. 进入scheduleBroadcastsLocked方法,其中采用Handler发送了BROADCAST_INTENT_MSG消息。
7.3. 在接受BROADCAST_INTENT_MSG Handler消息的之处,看到调用processNextBroadcast,也就是处理下一条广播。
7.4. 进入processNextBroadcast后看到其同步调用processNextBroadcastLocked,而processNextBroadcastLocked便是广播消费的主体了。

8. 下面进入processNextBroadcastLocked查看广播消费,这也挥应了如上逆推中的第3点,也就是mParallelBroadcasts与mDispatcher两个队列均在processNextBroadcastLocked中被处理,这就又回到这个700多行的大方法了,接下来又只能死磕了。由于具备上面第6点的顺推知识,此处顺推这个大方法也就轻松多了。此处核心关注点在这个广播如何被发送出去。

源码:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
8.1. 方法开始定义一个变量BroadcastRecord r,这便是广播的记录,代表着当前广播,也就是整个方法都围绕着这个r进行。
8.2. 接下里更新CPU等内容,跳过。
8.3. 接下来看到一个while循环(while (mParallelBroadcasts.size() > 0) {),mParallelBroadcasts是并行队列,所以接下来这个循环便是处理并行广播的。这个循环的核心调用为deliverToRegisteredReceiverLocked,将广播deliver至RegisteredReceiver类型的的Receiver中,根据第6点知识,RegisteredReceiver便是动态注册的广播列表。
8.3.1. 接下来又该开启支线任务了。deliverToRegisteredReceiverLocked又是一个200多行的大方法,继续死磕
8.3.2. 方法开始就定义一个boolean skip = false,表示是否要跳过这个广播的deliver。
8.3.3. 接下来大约150行都是各种if判断,if判断为真,则skip = true,if中判断各种权限之类的。如果最终skip = true,则跳过这次deliver,此处忽略这种被skip的情况,核心关注如何被发送。
8.3.4. 接下来继续判断权限,然后继续跳过deliver,跳过。
8.3.5. 如果是ordered的广播,还要处理oomAdj,跟如何发送也无关,继续跳过。
8.3.6. 接下来是一个try catch。看到if判断,一般来说filter.receiverList.app.inFullBackup为false(inFullBackup:Process is currently hosting a backup agent for backup or restore),即会走到else处,最终调用核心函数performReceiveLocked,开始接受广播。
8.3.6.1. 进入performReceiveLocked方法,根据不同条件,可能会转调scheduleRegisteredReceiver或performReceive。
8.3.6.2. scheduleRegisteredReceiver的定义在IApplicationThread.aidl中,此aidl有oneway标记,即异步执行。
源码:frameworks/base/core/java/android/app/IApplicationThread.aidl
8.3.6.3. performReceive的定义在IIntentReceiver.aidl中,同样,此aidl也有oneway标记,同样是异步执行。
源码:frameworks/base/core/java/android/content/IIntentReceiver.aidl
8.3.7. 从performReceiveLocked函数中出来后,回到processNextBroadcastLocked函数的while循环中,由于异步执行,所以while循环会很快发送完所有消息,让registeredReceivers分别去处理,所以这也就是广播的并行发送逻辑。
8.4. 接下去该到串行队列mDispatcher的处理了(由于一个广播可以被某应用动态注册为并行广播,也可以被其他应用静态注册为串行广播,所以接下去还需要处理串行队列)。接着往下看processNextBroadcastLocked方法。代码中也可看到注释:// Now take care of the next serialized one…。
8.5. 首先处理进程等待的特殊逻辑,此处不关心,跳过。
8.6. 接下去看到一个有着200行代码的巨大的do while循环,首先调用r = mDispatcher.getNextBroadcastLocked(now)去取串行队列中的BroadcastRecord r,在7.1中讲过,整个大方法都围绕着这个r进行。如果r为null,那么就该结束了,否则继续往下。
8.7. 接着判断些出错逻辑,如果当前时间超出了2倍的广播超时时间 * 接收者数,那么就弹出ANR。由于这里是特殊出错逻辑,非主流程,所以可无视,跳过此处接着看。
8.8. 接着的两个if判断都是在处理特殊出错逻辑,继续跳过。
8.9. 接下去是一个大的延时广播判断if (!r.deferred),deferred仅在addDeferredBroadcast被赋值为true。此处进入后if后,再调用if (mDispatcher.isDeferringLocked(receiverUid))判断是否需要延期处理,通常被判断为不需要,最后跳出这个大的if判断。
8.10. 继续往下,进行一系列系统广播调试所需的Track与Log等操作后,计算timeout时间后,调用setBroadcastTimeoutLocked
8.10.1. 接下来支线任务到setBroadcastTimeoutLocked中,看到在超时的时间点,将采用Handler发送BROADCAST_TIMEOUT_MSG消息
8.10.2. 在接受BROADCAST_TIMEOUT_MSG Handler消息的之处,看到broadcastTimeoutLocked
8.10.3. 进入broadcastTimeoutLocked,这就挥应第1点了,这个方法进行一系列判断后,最终调用AnrHelper.appNotResponding,走到ANR流程。绕了一大圈,终于找到广播触发ANR的导火索了。
8.10.4. setBroadcastTimeoutLocked在未来的时间点埋了个定时炸弹,如果时间到,还没拆除,就ANR。
8.11. 先回来,上面埋了定时炸弹后,串行广播还没有被发送,所以接着往下看。if判断是否需要直接调用调用receiver(if (nextReceiver instanceof BroadcastFilter)),此处不需要,跳过。
8.12. 接下去又是一个250行的skip判断逻辑,走完后skip = false,无需skip此广播。由于静态注册广播的App如果未启动,系统会将App启动,于是这一系列skip判断逻辑包括判断App是否处于已安装的正常状态,权限上是否允许启动,最后在必要时启动App。
8.13. 最终调用processCurBroadcastLocked进行广播的发送,然后便return结束这个长达700行的大方法。
8.14. processCurBroadcastLocked函数最终通过Binder调用IApplicationThread.scheduleReceiver。
源码:frameworks/base/core/java/android/app/IApplicationThread.aidl
等一下,此处的IApplicationThread仍然是oneway的,也就是说仍然是异步调用的。说好的同步呢?说好的串行呢?ANR定时炸弹何时拆除呢?

9. 为了解决上面的问题,这次从BroadcastReceiver再出发,在广播接收处理结束,会调用BroadcastReceiver的sendFinished方法,不管是并行还是串行,都会调用ActivityManager.finishReceiver。如果是串行,则会传递一些列参数,用于后续事项。如果是并行或没有接收器了,则基本传0或null了,目的只为告诉ActivityManagerService,这个广播最后一个接收器处理完了。

源码:frameworks/base/core/java/android/content/BroadcastReceiver.java

10. 在ActivityManager.finishReceiver中,调用getMatchingOrderedReceiver判断此Receiver是否为串行的:

  • 如果是,则调用BroadcastQueue.finishReceiverLocked,然后再调用BroadcastQueue.processNextBroadcastLocked。
  • 如果为否,即为并行,那么就什么都不处理。回忆一下如上并行处理逻辑,在processNextBroadcastLocked中,通过一个while循环将所有广播发送至动态注册的接收者中了,一次性就全部处理完了,也没有埋什么定时炸弹,所以对于并行广播,对于ActivityManagerService来说,没有什么后事需要处理了。

11. 接下来再次回到BroadcastQueue中,查看BroadcastQueue.finishReceiverLocked,方法名为结束Receiver,根据上面的分析,并行接收器名称为registeredReceivers,所以此方法是专门为串行接收器准备的。方法中主要是处理一些数据结构等善后工作,此处就不详细展开了。

12. 继续在BroadcastQueue中查看processNextBroadcastLocked。这个方法名有些眼熟,这又回到这个700多行代码的大方法了。根据上面串行的分析,对于串行广播队列,每一次processNextBroadcastLocked调用仅deliver一个广播,然后等待BroadcastReceiver的sendFinished,然后再行调用processNextBroadcastLocked来deliver下一个广播,如此来实现了串行。

而拆掉定时炸弹的工作也是在processNextBroadcastLocked中进行的,其中有cancelBroadcastTimeoutLocked调用,函数实现为移除Handler消息BROADCAST_TIMEOUT_MSG。

采用adb shell dumpsys activity broadcasts可以查看系统为广播设置的超时值(Broadcast parameters)

  • key=bcast_fg_constants:前台广播(默认超时10秒)
  • key=bcast_bg_constants:后台广播(默认超时60秒)
  • key=bcast_offload_constants:offload广播(默认超时60秒),已知需要耗费长时间的广播,如:BOOT_COMPLETED

adb logcat -s ActivityManager可以查看ANR的初步信息,如:

E ActivityManager: ANR in com.huya.mtp.mybroadcast
E ActivityManager: PID: 10653
E ActivityManager: Reason: Broadcast of Intent { act=com.huya.mtp.mybroadcast.BROADCAST_STATIC flg=0x10000010 pkg=com.huya.mtp.mybroadcast cmp=com.huya.mtp.mybroadcast/.MyBroadcastStaticReceiver (has extras) }

总结:Broadcast只有一种ANR,就是超时。对于并行广播,是不存在超时这一说的,也就是并行广播不会引发ANR。而对于串行广播,每次发送给接收器之前,都会通过Handler在未来设置一个定时炸弹,如果此接收器在超时之内没有完成处理,则引爆炸弹弹出ANR。按照编码的说法:

  • 调用Context.sendBroadcast,发送给动态注册的Receiver,则怎么都不会引发ANR
  • 调用Context.sendBroadcast,发送给静态注册的Receiver,如果Receiver在超时时间内未完成,则ANR
  • 调用Context.sendOrderedBroadcast,无论发送给动态或静态注册的Receiver,如果Receiver在超时时间内未完成,则ANR

Service ANR

在开始Service ANR前,再次同样问个问题:
创建一个Android Hello World工程,写一个服务,在Service.onCreate中调用SystemClock.sleep(300 * 1000),添加一个Button,点击后调用Context.startForegroundService启动这个服务。
运行App,点击一次按钮,一直等待,会不会产生ANR呢?结果是会,但通常看不到弹出ANR框,因为这个时候会崩溃。实际上此时即发生了ANR,又发生了崩溃。

上面硬磕完Broadcast的部分代码后,此处看Service的代码就轻松多了。仍然采用先逆推,后顺推的方式。

1. 继续从AnrHelper.appNotResponding出发,搜索调用地方。发现在ActiveServices中有serviceTimeout, serviceForegroundTimeout,最终调用到AnrHelper.appNotResponding,最终走到ANR流程。此处通过函数名基本得到是服务超时导致的ANR。

源码:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

2. 搜索serviceTimeout的调用,发现在ActivityManagerService中有唯一调用,这就又回到ActivityManagerService的代码中了。继续发现当收到Handler消息SERVICE_TIMEOUT_MSG后,便开始ANR流程。

源码:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

3. 继续搜索SERVICE_TIMEOUT_MSG,发现又回到ActiveServices中了,在其中有两处Handler.removeMessages调用(根据Broadcast源码猜测,则应该是Service拆定时炸弹逻辑了),有一处Handler.sendMessageDelayed(埋定时炸弹了)。

4. 先看Handler.sendMessageDelayed的封装方法scheduleServiceTimeoutLocked,翻译成中文大概是“锁定的调度服务超时”。上面Broadcast就多次出现schedule(调度),此处应该也就是到了Service被消费(被创建)的逻辑了。

5. 继续逆推看scheduleServiceTimeoutLocked的调用出,发现被bumpServiceExecutingLocked方法调用了。继续往上逆推,发现bumpServiceExecutingLocked又被realStartServiceLocked调用了,这就切实到了Service被创建的时刻了。这都几乎推到最开始了,下面转为顺推,应该会看得更清晰。

6. 这儿我们顺推Context.startService,进入调用链:

Context.startService --> ContextWrapper.startService --> ContextImpl.startService --> ContextImpl.startServiceCommon --> ActivityManagerService.startService

看到跟Broadcast的流程一摸一样,下面再验证一下Context.startForegroundService,进入调用链:

Context.startForegroundService --> ContextWrapper.startForegroundService --> ContextImpl.startForegroundService --> ContextImpl.startServiceCommon --> ActivityManagerService.startService

发现两者的流程也一致,foreground与否的差别在于startServiceCommon的第二个参数boolean requireForeground。

7. 继续深入ActivityManagerService.startService查看调用链

ActivityManagerService.startService ----> ActiveServices.startServiceLocked

经过重载来到了ActiveServices.startServiceLocked,这个方法大概250行代码,接下来进去看看。
7.1. 判断并设置final boolean callerFg的值,表示调用发起者是否为前台,这个值将贯穿整个Service的启动过程。ActivityManagerService.getRecordForAppLocked用于查找调用发起者的信息。
7.2. 接着调用retrieveServiceLocked查找服务启动接收者的信息,并最终取得ServiceRecord r。跟广播变量名一致,这个r变量是用于记录服务信息的。
7.3. 接下来一系列的判断,最终为ServiceRecord r的成员变量赋值。
7.4. 然后又是一系列的判断,最终来到内部的startServiceInnerLocked方法。
7.4.1. 接下去开启支线任务,进入到startServiceInnerLocked
7.4.2 继续进入,进行一些逻辑处理后,转调bringUpServiceLocked
7.4.3. 继续进入,进行一些逻辑处理后,转调realStartServiceLocked
7.4.4. 继续进入,进行一些逻辑处理后,调用bumpServiceExecutingLocked(此处也能挥应至如上第5点了)。在7.1点中提到boolean callerFg,此值一直传递到bumpServiceExecutingLocked中,最终赋值给r变量的executeFg,在后续设置Service超时时使用。
7.4.5. 继续进入,进行一些判断后,发现调用了scheduleServiceTimeoutLocked,这儿就埋上ANR定时炸弹了,如果是前台服务,超时设置为20秒,后台则为200秒(此处挥应如上第4点)。
7.4.6. 从scheduleServiceTimeoutLocked中退出
7.4.7. 从bumpServiceExecutingLocked中退出
7.4.8. 回到realStartServiceLocked继续往下看。最终通过Binder调用到ActivityThread.scheduleCreateService,回调已经来到Service的目标进程中了。scheduleCreateService发送Handler消息CREATE_SERVICE。
源码:frameworks/base/core/java/android/app/ActivityThread.java
7.4.9. 在接受CREATE_SERVICE处调用handleCreateService。
7.4.10. handleCreateService进行一系列检查后,调用service.onCreate,这就到了目标Service的onCreate方法了,也就是Service的@Override onCreate。
7.4.11. 上述onCreate执行完后,接着往下通过Binder调用到ActivityManagerService.serviceDoneExecuting。
7.4.12. 继续一路转调至ActiveServices.serviceDoneExecutingLocked
7.4.13. 然后再转调至ActiveServices.serviceDoneExecutingLocked,在其中经过一系列判断后,最终调用Handler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app)拆除ANR定时炸弹。
7.4.14. 这里补充说明一下。查看源码的同时,有些朋友可能会看到Handler消息ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,10秒的超时时间SERVICE_START_FOREGROUND_TIMEOUT,从命名上看,似乎说的是前台服务,而前台服务超时的20秒在7.4.5点已经讲过了,此处的这个10秒的意思是什么呢?实际上,如果调用Context.startForegroundService启动前台服务,则系统要求在这个10秒钟之内,Service必须调用Context.startForeground,否则会产生ANR,也会抛出RuntimeException崩溃。崩溃会将进程退掉,所以表面看上去是发生崩溃了,实际上ANR也发生了,只不过表面上看不到(崩溃是因为发送了Handler消息SCHEDULE_CRASH,代码在ActivityThread.java中)
logcat中的崩溃栈:

2021-11-08 15:20:13.931 11661-11661/com.huchao.myserviceanr E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.huchao.myserviceanr, PID: 11661
    android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{56a0ba u0 com.huchao.myserviceanr/.MyService}
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2005)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

logcat中的ANR信息:

E ActivityManager: ANR in com.huchao.myserviceanr
E ActivityManager: PID: 11661
E ActivityManager: Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{56a0ba u0 com.huchao.myserviceanr/.MyService}

adb logcat -s ActivityManager可以查看ANR的初步信息,如:

ActivityManager: ANR in com.huchao.myserviceanr
ActivityManager: PID: 11033
ActivityManager: Reason: executing service com.huchao.myserviceanr/.ForegroundService

总结:Service在创建超时会产生ANR,有3个超时值:

  • ActiveServices.SERVICE_START_FOREGROUND_TIMEOUT:10秒,开启前台服务超时时长,是指Context.startForegroundService调用后,Service中必须调用Context.startForeground,否则会同时发生ANR与崩溃。
  • ActiveServices.SERVICE_TIMEOUT:20秒,前台服务超时时长,是指Context.startForegroundService调用后,Service的onCreate执行的超时,如果超时则会发生ANR。
  • ActiveServices.SERVICE_BACKGROUND_TIMEOUT:200秒,后台服务超时时长,是指Context.startService调用后,Service的onCreate执行的超时,如果超时则会发生ANR。
    除了如上Service在创建时超时外,在bind绑定、unbind解绑、destroy销毁、start执行、反start执行时(某些特殊的服务Pending逻辑),也受如上条件限制。
    按照编码的说法:
    Service在onCreate、onDestroy、onStartCommand、onBind、onUnbind中执行都受超时限制,如果超时则ANR。

ContentProvider ANR

1. 继续来从AnrHelper.appNotResponding出发,搜索调用地方。发现在ContentProviderHelper中有appNotRespondingViaProvider方法,最终调用到AnrHelper.appNotResponding,最终走到ANR流程。此处通过函数名基本得到是ContentProvider导致的ANR。

源码:frameworks/base/services/core/java/com/android/server/am/ContentProviderHelper.java

2. 接着搜索appNotRespondingViaProvider,发现好几个地方有调用地方,接下来一步步看。

2.1. ContentProviderHelper中的appNotRespondingViaProvider调用。在ContentProviderHelper类中就有名为getProviderMimeType的方法调用appNotRespondingViaProvider,继续搜索getProviderMimeType,发现在ActivityManagerShellCommand中有调用getProviderMimeType,不过这个类是给adb shell am start-activity使用的,已超出本文范围,所以忽略。
源码:frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

2.2. ActivityManagerService, ActivityThread, ContextImpl中均有appNotRespondingViaProvider调用。不过这3个类仅为转调appNotRespondingViaProvider,并没有触发逻辑,所以先忽略。
源码:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/ContextImpl.java

2.3. ContentProviderClient中有appNotRespondingViaProvider调用,并且也有触发逻辑,接下去深入进去看看。
2.3.1. 在ContentProviderClient中有NotRespondingRunnable,运行到这个Runnable的run就会调用appNotRespondingViaProvider,调用栈流程为:

NotRespondingRunnable.run --> ApplicationContentResolver.appNotRespondingViaProvider --> ActivityThread.appNotRespondingViaProvider --> ActivityManagerService.appNotRespondingViaProvider --> ContentProviderHelper.appNotRespondingViaProvider --> AnrHelper.appNotResponding

调用流程就回到了最开始产生ANR处。

2.3.2. ContentProviderClient与ContentResolver类似,都是提供给Client使用的,都可以用来获取的数据,差别在于两者的使用场景不同。

  • 针对相同ContentProvider的多次调用,建议使用ContentProviderClient,但用完需要释放。
  • 针对不同ContentProvider的多次调用,建议使用ContentResolver。

2.3.3. 接下来看NotRespondingRunnable的触发逻辑就能找到ANR的产生处了,在名为setDetectNotResponding的方法中找到了new NotRespondingRunnable(),并且ContentProviderClient中还提供了beforeRemote, afterRemote来安装与拆除ANR定时炸弹。看样子是这儿了,继续看setDetectNotResponding方法,这是一个@SystemApi,并且不开放给第三方App使用(通过反射与Hook机制操作系统行为不在本文讨论范围内)。

2.4. 通过如上分析,所有常规三方App可达的ContentProvider ANR可行路径均已被堵死,也就是App自己的ContentProvider是不会产生ANR的。

总结:在Android 11版本中,ContentProvider有好几处ANR触发逻辑,但均已被堵死,也就是App的ContentProvider是不会触发产生ANR的。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>