Android 源码分析ANR
来源:互联网 发布:重大资产重组知乎 编辑:程序博客网 时间:2024/05/20 08:27
先普及一下基本知识:
什么是ANR
ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。在Android中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会监测应用程序的响应时间,如果应用程序主线程(即UI线程)在超时时间内对输入事件没有处理完毕,或者对特定操作没有执行完毕,就会出现ANR。对于输入事件没有处理完毕产生的ANR,Android会显示一个对话框,提示用户当前应用程序没有响应,用户可以选择继续等待或者关闭这个应用程序(也就是杀掉这个应用程序的进程)。
产生ANR的三个必要条件
1.主线程
2.有输入事件
3.处理超时
产生ANR的情况
1.主线程对输入事件5秒没有处理完
2.主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕
3.主线程在执行Service的各个生命周期函数时20秒内没有执行完毕
产生ANR对于Android开发再熟悉不过了,每到它发生的时候,很影响用户体验,是一个很严重的问题,严重到必须要通过关闭整个进程才能让手机正常运行。第二种和第三种情况都不会弹出dialog提示,我们通过底层源码分析第一种情况ANR的产生。
事件传递的源头是InputReader,然后用InputDispatcher发送,我们看如何发送这个事件。
bool InputDispatcher::dispatchMotionLocked( nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { ...... bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; // Identify targets. Vector<InputTarget> inputTargets; bool conflictingPointerActions = false; int32_t injectionResult; if (isPointerEvent) { // Pointer event. (eg. touchscreen) injectionResult = findTouchedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime, &conflictingPointerActions); } else { // Non touch event. (eg. trackball) injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); } if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; } ...... dispatchEventLocked(currentTime, entry, inputTargets); return true;}
在dispatchEventLocked发送事件之前,会先去判断这个事件是点击事件(isPointEvent)还是其他事件。继续看findTouchedWindowTargetLocked。
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { enum InjectionPermission { INJECTION_PERMISSION_UNKNOWN, INJECTION_PERMISSION_GRANTED, INJECTION_PERMISSION_DENIED }; nsecs_t startTime = now(); ...... // Ensure all touched foreground windows are ready for new input. for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { // Check whether the window is ready for more input. String8 reason = checkWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry, "touched"); if (!reason.isEmpty()) { injectionResult = handleTargetsNotReadyLocked(currentTime, entry, NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string()); goto Unresponsive; } } } ...... return injectionResult;}
这是一个很长的方法,大体是判断这个事件的类型,获取能够处理这个事件的forceground window,如果这个window不能够继续处理事件,就是说这个window的主线程被某些耗时操作占据,我们继续看handleTargetsNotReadyLocked这个方法。
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, const sp<InputApplicationHandle>& applicationHandle, const sp<InputWindowHandle>& windowHandle, nsecs_t* nextWakeupTime, const char* reason) { if (applicationHandle == NULL && windowHandle == NULL) { if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {#if DEBUG_FOCUS ALOGD("Waiting for system to become ready for input. Reason: %s", reason);#endif mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; mInputTargetWaitStartTime = currentTime; mInputTargetWaitTimeoutTime = LONG_LONG_MAX; mInputTargetWaitTimeoutExpired = false; mInputTargetWaitApplicationHandle.clear(); } } else { if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {#if DEBUG_FOCUS ALOGD("Waiting for application to become ready for input: %s. Reason: %s", getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), reason);#endif nsecs_t timeout; if (windowHandle != NULL) { timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else if (applicationHandle != NULL) { timeout = applicationHandle->getDispatchingTimeout( DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else { timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; } mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; mInputTargetWaitStartTime = currentTime; mInputTargetWaitTimeoutTime = currentTime + timeout; mInputTargetWaitTimeoutExpired = false; mInputTargetWaitApplicationHandle.clear(); if (windowHandle != NULL) { mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle; } if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) { mInputTargetWaitApplicationHandle = applicationHandle; } } } if (mInputTargetWaitTimeoutExpired) { return INPUT_EVENT_INJECTION_TIMED_OUT; } if (currentTime >= mInputTargetWaitTimeoutTime) { onANRLocked(currentTime, applicationHandle, windowHandle, entry->eventTime, mInputTargetWaitStartTime, reason); // Force poll loop to wake up immediately on next iteration once we get the // ANR response back from the policy. *nextWakeupTime = LONG_LONG_MIN; return INPUT_EVENT_INJECTION_PENDING; } else { // Force poll loop to wake up when timeout is due. if (mInputTargetWaitTimeoutTime < *nextWakeupTime) { *nextWakeupTime = mInputTargetWaitTimeoutTime; } return INPUT_EVENT_INJECTION_PENDING; }}
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
主线程对输入事件的超时是5s,设置了最后截止时间,如果时间未到进入下一个循环。
如果返回INPUT_EVENT_INJECTION_PENDING,将事件下个loop继续处理。
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; }
回到dispatchMotionEventLocked,如果返回值是INPUT_EVENT_INJECTION_PENDING,就返回false,而不继续调用dispatchEventLocked发送事件。我们看后续处理。
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { nsecs_t currentTime = now(); ...... case EventEntry::TYPE_MOTION: { MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) { dropReason = DROP_REASON_APP_SWITCH; } if (dropReason == DROP_REASON_NOT_DROPPED && isStaleEventLocked(currentTime, typedEntry)) { dropReason = DROP_REASON_STALE; } if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { dropReason = DROP_REASON_BLOCKED; } done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); break; } default: ALOG_ASSERT(false); break; } if (done) { if (dropReason != DROP_REASON_NOT_DROPPED) { dropInboundEventLocked(mPendingEvent, dropReason); } mLastDropReason = dropReason; releasePendingEventLocked(); *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately }}
这里done = false,因为dispatchMotionEventLocked返回false,等下一次dispatchOnce再发送一次。
如果时间已过,就在很短的时间内进入下一个事件循环,进入onANRLocked
void InputDispatcher::onANRLocked( nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle, const sp<InputWindowHandle>& windowHandle, nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) { float dispatchLatency = (currentTime - eventTime) * 0.000001f; float waitDuration = (currentTime - waitStartTime) * 0.000001f; ALOGI("Application is not responding: %s. " "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s", getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), dispatchLatency, waitDuration, reason); // Capture a record of the InputDispatcher state at the time of the ANR. time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); char timestr[64]; strftime(timestr, sizeof(timestr), "%F %T", &tm); mLastANRState.clear(); mLastANRState.append(INDENT "ANR:\n"); mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr); mLastANRState.appendFormat(INDENT2 "Window: %s\n", getApplicationWindowLabelLocked(applicationHandle, windowHandle).string()); mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency); mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration); mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason); dumpDispatchStateLocked(mLastANRState); CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doNotifyANRLockedInterruptible); commandEntry->inputApplicationHandle = applicationHandle; commandEntry->inputWindowHandle = windowHandle; commandEntry->reason = reason;}
进入doANRlockedInterruptible
void InputDispatcher::doNotifyANRLockedInterruptible( CommandEntry* commandEntry) { mLock.unlock(); nsecs_t newTimeout = mPolicy->notifyANR( commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle, commandEntry->reason); mLock.lock(); resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputWindowHandle != NULL ? commandEntry->inputWindowHandle->getInputChannel() : NULL);}
mPolicy->notifyANR,的实现在com_android_server_input_InputManagerService.cpp这个文件,继续传递到Java世界的InputManagerService.java。而InputManagerService处理的对象正是windowManagerService的对象mInputMonitor。
我们回到InputDispatcher的检查forcegroudWindow是否有效的方法。
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime, const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry, const char* targetType) { // If the window is paused then keep waiting. if (windowHandle->getInfo()->paused) { return String8::format("Waiting because the %s window is paused.", targetType); } // If the window's connection is not registered then keep waiting. ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel()); if (connectionIndex < 0) { return String8::format("Waiting because the %s window's input channel is not " "registered with the input dispatcher. The window may be in the process " "of being removed.", targetType); } // If the connection is dead then keep waiting. sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); if (connection->status != Connection::STATUS_NORMAL) { return String8::format("Waiting because the %s window's input connection is %s." "The window may be in the process of being removed.", targetType, connection->getStatusLabel()); } // If the connection is backed up then keep waiting. if (connection->inputPublisherBlocked) { return String8::format("Waiting because the %s window's input channel is full. " "Outbound queue length: %d. Wait queue length: %d.", targetType, connection->outboundQueue.count(), connection->waitQueue.count()); } // Ensure that the dispatch queues aren't too far backed up for this event. if (eventEntry->type == EventEntry::TYPE_KEY) { // If the event is a key event, then we must wait for all previous events to // complete before delivering it because previous events may have the // side-effect of transferring focus to a different window and we want to // ensure that the following keys are sent to the new window. // // Suppose the user touches a button in a window then immediately presses "A". // If the button causes a pop-up window to appear then we want to ensure that // the "A" key is delivered to the new pop-up window. This is because users // often anticipate pending UI changes when typing on a keyboard. // To obtain this behavior, we must serialize key events with respect to all // prior input events. if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) { return String8::format("Waiting to send key event because the %s window has not " "finished processing all of the input events that were previously " "delivered to it. Outbound queue length: %d. Wait queue length: %d.", targetType, connection->outboundQueue.count(), connection->waitQueue.count()); } } else { // Touch events can always be sent to a window immediately because the user intended // to touch whatever was visible at the time. Even if focus changes or a new // window appears moments later, the touch event was meant to be delivered to // whatever window happened to be on screen at the time. // // Generic motion events, such as trackball or joystick events are a little trickier. // Like key events, generic motion events are delivered to the focused window. // Unlike key events, generic motion events don't tend to transfer focus to other // windows and it is not important for them to be serialized. So we prefer to deliver // generic motion events as soon as possible to improve efficiency and reduce lag // through batching. // // The one case where we pause input event delivery is when the wait queue is piling // up with lots of events because the application is not responding. // This condition ensures that ANRs are detected reliably. if (!connection->waitQueue.isEmpty() && currentTime >= connection->waitQueue.head->deliveryTime + STREAM_AHEAD_EVENT_TIMEOUT) { return String8::format("Waiting to send non-key event because the %s window has not " "finished processing certain input events that were delivered to it over " "%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.", targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f, connection->waitQueue.count(), (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f); } } return String8::empty();}
checkwindowReadyForMoreInputLocked方法,这个方法之前我们提到过,正常的情况下返回的string应该是空的,让我们看一下非空的情况。
触摸事件可以立即发送到窗口,因为用户预期触摸当时可见的任何东西。即使焦点改变或新窗口出现后,触摸事件的目的是传递到无论什么时候发生在屏幕上的窗口。通用运动事件,如鼠标或操纵杆事件是棘手的。如同key事件,通用motion事件被传递到焦点窗口。不像key事件,一般的motion件不倾向于转移焦点到其他窗口,他们被序列化不重要。所以我们传递通用motion事件,尽快提高效率,通过分批减少滞后。
我们暂停输入事件传递是当等待队列事件堆积时,因为应用程序没有响应。这个条件保证ANR检测的可靠。
- android-ANR源码分析
- Android 源码分析ANR
- Android ANR源码原理分析
- android ANR源码分析 --- 之一
- android ANR源码分析 --- 之二
- android ANR源码分析 --- 之三
- android ANR源码分析 --- 之四
- Android-ANR-Android ANR分析
- 【Android】【ANR】如何分析解决Android ANR
- Android ANR问题分析
- [Android] 分析ANR错误
- android anr分析方法
- android anr分析方法
- 【转】Android ANR分析
- Android ANR Log分析
- android anr分析方法
- android ANR分析
- android ANR 分析
- 第四届蓝桥杯【省赛试题8】翻硬币
- CentOS6.7_Mysql5.6_使用mydumper全库备份
- ajax提交form表单后页面自动刷新
- Reverse Pairs (第二周:分治法)
- css3 图片实现毛玻璃特效
- Android 源码分析ANR
- zhinanzhen
- 三维细胞结构生物显微影像分析处理软件
- Android中文简体繁体互相转换的实现(繁简互转)
- JS回调函数总结
- 统计学习方法——模型与问题分类
- java中volatile关键字的含义
- 数据挖掘/机器学习算法--C4.5以及决策树(Decision Tree)
- [leetcode53]两种思路解决的Maximun Subarray