Android ANR 问题第二弹------Input事件是如何超时导致ANR的

在Android ANR 问题第一弹中,我们介绍Android ANR问题分为三类:Input,Receiver,Service。我们现在就先来详细的介绍Input事件超时是如何导致ANR问题发生的,我们只有从原理上去了解Input超时的本质,才能更好的分析解决实际开发中遇到的问题。本文会以Android8.1的代码为准,来简要分析Input事件超时。

在分析Input超时之前,我们先来简单的介绍一下Android的Input系统。Android Input体系中,大致有两种类型的事件,实体按键key事件,屏幕点击触摸事件,当然如果根据事件类型的不同我们还能细分为基础实体按键的key(power,volume up/down,recents,back,home),实体键盘按键,屏幕点击(多点,单点),屏幕滑动等等的事件。在Android整个Input体系中有三个格外重要的成员:Eventhub,InputReader,InputDispatcher。它们分别担负着各自不同的职责,Eventhub负责监听/dev/input产生Input事件,InputReader负责从Eventhub读取事件,并将读取的事件发给InputDispatcher,InputDispatcher则根据实际的需要具体分发给当前手机获得焦点实际的Window。当然它们三者之间有工作远比我介绍的要复杂的很多。

好了当我们知道什么是Input的时候,我们现在就开始分析Input是如何超时导致ANR的。我们常说Input超时,都是指的是Input事件分发超时,因此整个超时计算以及触发都在InputDispatcher这个类中。其代码路径如下:/frameworks/native/services/inputflinger/InputDispatcher.cpp,Input分发事件的时候就是不断执行InputDispatcher的threadLoop来读取Input事件,并调用dispatchOnce进行分发事件的。当然如果没有Input事件的时候,他会执行mLooper->pollOnce,进入等待状态。这个就和Android应用UI主线程的Looper一样,MessageQueue里面没有消息的时候,等待于nativePollOnce方法,其实最终还是调用Looper->pollOnce进入等待状态。

 
  1. bool InputDispatcherThread::threadLoop() {

  2. mDispatcher->dispatchOnce();

  3. return true;

  4. }

我们知道InputDispatcher会通过dispatchOnce不断的读取并分发Input事件,因此我直接来看InputDispatcher::dispatchOnceInnerLocked该方法,其中代码并非整个代码,我这边只列取关键代码进行分析,而且整个分析过程主要以按键事件为主要分析对象。

 
  1. void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {

  2. nsecs_t currentTime = now();//记录事件分发的第一时间点,很重要,此参数会不断在接下来的方法中作为参数进行传递。

  3. // Ready to start a new event.

  4. // If we don't already have a pending event, go grab one.

  5. if (! mPendingEvent) { //只有但前mPendingEvent(正在分发的事件)为空的时候才进入

  6. //从注释中可以看出这里就是获取一个Input事件,并且重置ANR时间计算的相关参数

  7. // Get ready to dispatch the event.

  8. resetANRTimeoutsLocked();

  9. }

  10. switch (mPendingEvent->type) {

  11. case EventEntry::TYPE_KEY: {

  12. KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);

  13. //找到Input事件,让我们发起来

  14. done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);

  15. break;

  16. }

  17. }

  18. if (done) {

  19. if (dropReason != DROP_REASON_NOT_DROPPED) {

  20. dropInboundEventLocked(mPendingEvent, dropReason);//这里稍微提一下,一般打出的一些drop***event的log都是从这里输出的

  21. }

  22. }

  23. }

来看下resetANRTimeoutsLocked方法

 
  1. void InputDispatcher::resetANRTimeoutsLocked() {

  2. // Reset input target wait timeout.

  3. mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;

  4. mInputTargetWaitApplicationHandle.clear();

  5. }

继续事件分发dispatchKeyLocked,gogogo

 
  1. bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,

  2. DropReason* dropReason, nsecs_t* nextWakeupTime) {

  3. // Identify targets.

  4. Vector<InputTarget> inputTargets;

  5. int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,

  6. entry, inputTargets, nextWakeupTime); file descriptors from//这边会找到当前有焦点的窗口window,并根据条件触发ANR

  7.  
  8. addMonitoringTargetsLocked(inputTargets);

  9.  
  10. // Dispatch the key.

  11. dispatchEventLocked(currentTime, entry, inputTargets);//继续执行事件分发流程

  12. return true;

  13. }

重点分析findFocusedWindowTargetsLocked该方法

 
  1. int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,

  2. const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {

  3. int32_t injectionResult;

  4. String8 reason;

  5. // If there is no currently focused window and no focused application

  6. // then drop the event.

  7. if (mFocusedWindowHandle == NULL) {

  8. if (mFocusedApplicationHandle != NULL) {

  9. injectionResult = handleTargetsNotReadyLocked(currentTime, entry,

  10. mFocusedApplicationHandle, NULL, nextWakeupTime,

  11. "Waiting because no window has focus but there is a "

  12. "focused application that may eventually add a window "

  13. "when it finishes starting up.");

  14. goto Unresponsive;

  15. }//看到这里,有没有一丝的惊喜,是不是发现monkey test的时候经常遇到类似log的ANR?典型的无窗口,有应用的ANR问题,这里我们就需要了解Android应用的启动流程了(后续准备写一篇Android应用启动流程详细分析的文章),一般此类问题都是Android应用首次启动时会发生此类问题,此时我们应用本身需要检查一下我们的Android应用重写的Application onCreate方法,Android应用的启动界面是否在onCreate onStart方法中是否存在耗时操作。当然不排除系统原因造成的启动慢,直接导致ANR问题发生的情况

  16.  
  17. ALOGI("Dropping event because there is no focused window or focused application.");

  18. injectionResult = INPUT_EVENT_INJECTION_FAILED;

  19. goto Failed;

  20. }

  21.  
  22.  
  23. // Check whether the window is ready for more input.//这里将会进入更为详细更多种类的ANR触发过程

  24. reason = checkWindowReadyForMoreInputLocked(currentTime,

  25. mFocusedWindowHandle, entry, "focused");

  26. if (!reason.isEmpty()) {//一旦checkWindowReadyForMoreInputLocked返回不为空,怎说明存在应用ANR

  27. injectionResult = handleTargetsNotReadyLocked(currentTime, entry,

  28. mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());

  29. goto Unresponsive;

  30. }

  31.  
  32. // Success! Output targets.

  33. injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;

  34. addWindowTargetLocked(mFocusedWindowHandle,

  35. InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),

  36. inputTargets);

  37.  
  38. // Done.

  39. Failed:

  40. Unresponsive:

  41. nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);

  42. updateDispatchStatisticsLocked(currentTime, entry,

  43. injectionResult, timeSpentWaitingForApplication);

  44. #if DEBUG_FOCUS

  45. ALOGD("findFocusedWindow finished: injectionResult=%d, "

  46. "timeSpentWaitingForApplication=%0.1fms",

  47. injectionResult, timeSpentWaitingForApplication / 1000000.0);

  48. #endif

  49. return injectionResult;

  50. }

各种ANR种类判断checkWindowReadyForMoreInputLocked,这里就不去进行详细的分析了,毕竟源码的注释很了然了。

 
  1. String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,

  2. const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,

  3. const char* targetType) {

  4. // If the window is paused then keep waiting.

  5. if (windowHandle->getInfo()->paused) {

  6. return String8::format("Waiting because the %s window is paused.", targetType);

  7. }

  8.  
  9. // If the window's connection is not registered then keep waiting.

  10. ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());

  11. if (connectionIndex < 0) {

  12. return String8::format("Waiting because the %s window's input channel is not "

  13. "registered with the input dispatcher. The window may be in the process "

  14. "of being removed.", targetType);

  15. }

  16.  
  17. // If the connection is dead then keep waiting.

  18. sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);

  19. if (connection->status != Connection::STATUS_NORMAL) {

  20. return String8::format("Waiting because the %s window's input connection is %s."

  21. "The window may be in the process of being removed.", targetType,

  22. connection->getStatusLabel());

  23. }

  24.  
  25. // If the connection is backed up then keep waiting.

  26. if (connection->inputPublisherBlocked) {

  27. return String8::format("Waiting because the %s window's input channel is full. "

  28. "Outbound queue length: %d. Wait queue length: %d.",

  29. targetType, connection->outboundQueue.count(), connection->waitQueue.count());

  30. }

  31.  
  32. // Ensure that the dispatch queues aren't too far backed up for this event.

  33. if (eventEntry->type == EventEntry::TYPE_KEY) {

  34. // If the event is a key event, then we must wait for all previous events to

  35. // complete before delivering it because previous events may have the

  36. // side-effect of transferring focus to a different window and we want to

  37. // ensure that the following keys are sent to the new window.

  38. //

  39. // Suppose the user touches a button in a window then immediately presses "A".

  40. // If the button causes a pop-up window to appear then we want to ensure that

  41. // the "A" key is delivered to the new pop-up window. This is because users

  42. // often anticipate pending UI changes when typing on a keyboard.

  43. // To obtain this behavior, we must serialize key events with respect to all

  44. // prior input events.

  45. if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {

  46. return String8::format("Waiting to send key event because the %s window has not "

  47. "finished processing all of the input events that were previously "

  48. "delivered to it. Outbound queue length: %d. Wait queue length: %d.",

  49. targetType, connection->outboundQueue.count(), connection->waitQueue.count());

  50. }

  51. } else {

  52. // Touch events can always be sent to a window immediately because the user intended

  53. // to touch whatever was visible at the time. Even if focus changes or a new

  54. // window appears moments later, the touch event was meant to be delivered to

  55. // whatever window happened to be on screen at the time.

  56. //

  57. // Generic motion events, such as trackball or joystick events are a little trickier.

  58. // Like key events, generic motion events are delivered to the focused window.

  59. // Unlike key events, generic motion events don't tend to transfer focus to other

  60. // windows and it is not important for them to be serialized. So we prefer to deliver

  61. // generic motion events as soon as possible to improve efficiency and reduce lag

  62. // through batching.

  63. //

  64. // The one case where we pause input event delivery is when the wait queue is piling

  65. // up with lots of events because the application is not responding.

  66. // This condition ensures that ANRs are detected reliably.

  67. if (!connection->waitQueue.isEmpty()

  68. && currentTime >= connection->waitQueue.head->deliveryTime

  69. + STREAM_AHEAD_EVENT_TIMEOUT) {

  70. return String8::format("Waiting to send non-key event because the %s window has not "

  71. "finished processing certain input events that were delivered to it over "

  72. "%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.",

  73. targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,

  74. connection->waitQueue.count(),

  75. (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);

  76. }

  77. }

  78. return String8::empty();

  79. }

根据各种reason,判断是否已经超时,触发ANR

 
  1. int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,

  2. const EventEntry* entry,

  3. const sp<InputApplicationHandle>& applicationHandle,

  4. const sp<InputWindowHandle>& windowHandle,

  5. nsecs_t* nextWakeupTime, const char* reason) {

  6. if (applicationHandle == NULL && windowHandle == NULL) {//无应用,无窗口,进入一次,继续等待应用,不触发ANR

  7. if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {

  8. #if DEBUG_FOCUS

  9. ALOGD("Waiting for system to become ready for input. Reason: %s", reason);

  10. #endif

  11. mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;

  12. mInputTargetWaitStartTime = currentTime;

  13. mInputTargetWaitTimeoutTime = LONG_LONG_MAX;

  14. mInputTargetWaitTimeoutExpired = false;

  15. mInputTargetWaitApplicationHandle.clear();

  16. }

  17. } else {

  18. if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {

  19. #if DEBUG_FOCUS//这里一般是有应用(application已经创建),无窗口,或者有应用,有窗口ANR的情形,一般同一个窗口至进入一次该方法

  20. ALOGD("Waiting for application to become ready for input: %s. Reason: %s",

  21. getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),

  22. reason);

  23. #endif

  24. nsecs_t timeout;

  25. if (windowHandle != NULL) {

  26. timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);//5s超时

  27. } else if (applicationHandle != NULL) {

  28. timeout = applicationHandle->getDispatchingTimeout(

  29. DEFAULT_INPUT_DISPATCHING_TIMEOUT);//5s超时

  30. } else {

  31. timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;//5s超时

  32. }

  33.  
  34. mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;//超时等待原因

  35. mInputTargetWaitStartTime = currentTime;//记录当前分发事件为第一次分发时间

  36. mInputTargetWaitTimeoutTime = currentTime + timeout;//设置超时

  37. mInputTargetWaitTimeoutExpired = false;//超时是否过期

  38. mInputTargetWaitApplicationHandle.clear();//清除记录当前等待的应用

  39.  
  40. if (windowHandle != NULL) {

  41. mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;//记录当前等待的应用

  42. }

  43. if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {

  44. mInputTargetWaitApplicationHandle = applicationHandle;

  45. }//记录当前等待的应用,针对无窗口,有应用

  46. }

  47. }

  48.  
  49. if (mInputTargetWaitTimeoutExpired) {

  50. return INPUT_EVENT_INJECTION_TIMED_OUT;

  51. }

  52.  
  53. if (currentTime >= mInputTargetWaitTimeoutTime) {//当前时间已经大于超时时间,说明应用有时间分发超时了,需要触发ANR

  54. onANRLocked(currentTime, applicationHandle, windowHandle,

  55. entry->eventTime, mInputTargetWaitStartTime, reason);

  56.  
  57. // Force poll loop to wake up immediately on next iteration once we get the

  58. // ANR response back from the policy.

  59. *nextWakeupTime = LONG_LONG_MIN;

  60. return INPUT_EVENT_INJECTION_PENDING;

  61. } else {

  62. // Force poll loop to wake up when timeout is due.

  63. if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {

  64. *nextWakeupTime = mInputTargetWaitTimeoutTime;

  65. }

  66. return INPUT_EVENT_INJECTION_PENDING;

  67. }

  68. }

我们来理一理该方法:

 

  • 当有事件第一次分发的时候,我们需要注意mFocusedWindowHandle和mFocusedApplicationHandle,暂不考虑无应用,无窗口的情况,这两个参数都是通过WMS在应用启动addWindow或者有Window切换的时候,通过JNI设置到InputDispatcher中的,所以我们在分发事件的时候,只会记录Input事件第一次分发时的时间点,并设置该事件超时的相关参数。
  • 当InputDispatcher再次执行dispatchOnceInnerLocked的时候,发现当前的mPendingEvent不为空,所以不会重置ANR相关的timeout参数,因此只会不停的判断当前的时间是否大于mInputTargetWaitTimeoutTime,如果大于则触发ANR。
  • 什么时候会重置ANR相关的timeout参数呢?分发到新的Input事件时(重置),也就是mpendingevent处理完(重置),又有新的Input事件产生的时候,焦点应用更新的时候,InputDispatcher自身重置的时候。
  • 当Input事件分发超时导致ANR时,真正的ANR发生的第一时间所以应该是InputDispatcherLog打出的时间点,当调用onANRLocked层层调用最终触发appNotResponding打印event log ,ActivityManager anr log,记录trace,因此我们说event log ,ActivityManager anr log,trace具有参考性,并不绝对,并无道理。

到此我们也应该对Input事件导致的ANR问题有一个基本的了解了,我们也能更快更准的定位ANR问题的发生的原因。当然,并不是大家看完本篇文章,就能立马很好的分析ANR问题了,前提是我们自身还是要有充足的知识储备,我们都在学习的路上,还是一句话,不为繁华异匠心,与君共勉之。

 

转载:https://blog.csdn.net/abm1993/article/details/80461752

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值