您的位置:首页 > 移动开发 > Android开发

Android 源码分析ANR

2017-03-04 15:14 363 查看
先普及一下基本知识:

什么是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检测的可靠。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: