目标窗口查找时,作为派发目标的窗口必须已经准备好接收新的输入事件,否则判定窗口处于未响应状态,终止事件的派发过程,并在一段时间后再试。倘若5s后窗口仍然未准备好接收输入事件,将导致ANR。直接引发ANR的原因有很多,
例如Activity生命周期函数调用超时,
服务启动超时
以及最常见的输入事件处理超时等。
- Service ANR:前台20s,后台200s;startForeground超时10s
- Broadcast ANR:前台10s,后台60s
- Input ANR:按键或触摸事件在5s内无响应
- ContentProvider ANR:10s,少见
下面从输入事件超时的角度讨论ANR的产生原因与过程
inputANR 分为两种
无响应anr: 应用连接正常但是未相应事件,并且发生了超时
无焦点窗口anr: 当前有焦点应用,但是无焦点窗口,并且超时
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptable()) {
nextWakeupTime = LLONG_MIN;
}
// If we are still waiting for ack on some events,
// we might have to wake up earlier to check if an app is anr'ing.
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
if (nextWakeupTime == LLONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
nsecs_t InputDispatcher::processAnrsLocked() {
const nsecs_t currentTime = now();
nsecs_t nextAnrCheck = LLONG_MAX;
// Check if we are waiting for a focused window to appear. Raise ANR if waited too long
if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
if (currentTime >= *mNoFocusedWindowTimeoutTime) {
processNoFocusedWindowAnrLocked();// 无焦点anr
mAwaitedFocusedApplication.reset();
mNoFocusedWindowTimeoutTime = std::nullopt;
return LLONG_MIN;
} else {
// Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
nextAnrCheck = *mNoFocusedWindowTimeoutTime;
}
}
// Check if any connection ANRs are due
nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
if (currentTime < nextAnrCheck) { // most likely scenario
return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
}
// If we reached here, we have an unresponsive connection.
std::shared_ptr<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
if (connection == nullptr) {
ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
return nextAnrCheck;
}
connection->responsive = false;
// Stop waking up for this unresponsive connection
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
onAnrLocked(connection);// 无响应anr
return LLONG_MIN;
}
mNoFocusedWindowTimeoutTime 和 mAwaitedFocusedApplication 的设置是在 InputDispatcher::findFocusedWindowTargetLocked
函数中:
sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
InputEventInjectionResult& outInjectionResult) {
std::string reason;
outInjectionResult = InputEventInjectionResult::FAILED; // Default result
int32_t displayId = getTargetDisplayId(entry);
sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// If there is no currently focused window and no focused application
// then drop the event.
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
ftl::enum_string(entry.type).c_str(), displayId);
return nullptr;
}
// Drop key events if requested by input feature
if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
return nullptr;
}
// Compatibility behavior: raise ANR if there is a focused application, but no focused window.
// Only start counting when we have a focused event to dispatch. The ANR is canceled if we
// start interacting with another application via touch (app switch). This code can be removed
// if the "no focused window ANR" is moved to the policy. Input doesn't know whether
// an app is expected to have a focused window.
if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
if (!mNoFocusedWindowTimeoutTime.has_value()) {
// We just discovered that there's no focused window. Start the ANR timer
std::chrono::nanoseconds timeout = focusedApplicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
mNoFocusedWindowTimeoutTime = currentTime + timeout.count();
mAwaitedFocusedApplication = focusedApplicationHandle;
mAwaitedApplicationDisplayId = displayId;
ALOGW("Waiting because no window has focus but %s may eventually add a "
"window when it finishes starting up. Will wait for %" PRId64 "ms",
mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
*nextWakeupTime = *mNoFocusedWindowTimeoutTime;
outInjectionResult = InputEventInjectionResult::PENDING;
return nullptr;
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
ftl::enum_string(entry.type).c_str());
return nullptr;
} else {
// Still waiting for the focused window
outInjectionResult = InputEventInjectionResult::PENDING;
return nullptr;
}
}
// we have a valid, non-null focused window
resetNoFocusedWindowTimeoutLocked();
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
return nullptr;
}
if (focusedWindowHandle->getInfo()->inputConfig.test(
WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
outInjectionResult = InputEventInjectionResult::PENDING;
return nullptr;
}
// 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 (entry.type == EventEntry::Type::KEY) {
if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
*nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
outInjectionResult = InputEventInjectionResult::PENDING;
return nullptr;
}
}
outInjectionResult = InputEventInjectionResult::SUCCEEDED;
return focusedWindowHandle;
}
可以看出引发 无焦点ANR 的两个条件:
1.mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr 并且 currentTime >= *mNoFocusedWindowTimeoutTime
mNoFocusedWindowTimeoutTime 和 mAwaitedFocusedApplication 的设置是在 InputDispatcher::findFocusedWindowTargetsLocked 函数中:
当有焦点应用但没有焦点窗口,设置 mNoFocusedWindowTimeoutTime 的值,开启 ANR 倒计时。对于当前事件返回 PENDING 。
接着在当前的派发循环中执行 processAnrsLocked 函数判断是否引发 ANR。
重置 mNoFocusedWindowTimeoutTime 和 mAwaitedFocusedApplication 是在 InputDispatcher::resetNoFocusedWindowTimeoutLocked 中。
执行 resetNoFocusedWindowTimeoutLocked 的几个时机:
(1)InputDispatcher::findFocusedWindowTargetLocked中,当等待有焦点窗口后;
(2)InputDispatcher::setFocusedApplication 中,切换焦点应用后;
(3)InputDispatcher::setInputDispatchMode 中;
(4) InputDispatcher::resetAndDropEverythingLocked
无焦点anr
void InputDispatcher::processNoFocusedWindowAnrLocked() {
// Check if the application that we are waiting for is still focused.
std::shared_ptr<InputApplicationHandle> focusedApplication =
getValueByKey(mFocusedApplicationHandlesByDisplay, mAwaitedApplicationDisplayId);
if (focusedApplication == nullptr ||
focusedApplication->getApplicationToken() !=
mAwaitedFocusedApplication->getApplicationToken()) {
// Unexpected because we should have reset the ANR timer when focused application changed
ALOGE("Waited for a focused window, but focused application has already changed to %s",
focusedApplication->getName().c_str());
return; // The focused application has changed.
}
const sp<WindowInfoHandle>& focusedWindowHandle =
getFocusedWindowHandleLocked(mAwaitedApplicationDisplayId);
if (focusedWindowHandle != nullptr) {
return; // We now have a focused window. No need for ANR.
}
onAnrLocked(mAwaitedFocusedApplication);
}
void InputDispatcher::onAnrLocked(std::shared_ptr<InputApplicationHandle> application) {
std::string reason =
StringPrintf("%s does not have a focused window", application->getName().c_str());
updateLastAnrStateLocked(*application, reason);
auto command = [this, application = std::move(application)]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
mPolicy.notifyNoFocusedWindowAnr(application);
};
postCommandLocked(std::move(command));
}
无响应anr
void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) {
if (connection == nullptr) {
LOG_ALWAYS_FATAL("Caller must check for nullness");
}
// Since we are allowing the policy to extend the timeout, maybe the waitQueue
// is already healthy again. Don't raise ANR in this situation
if (connection->waitQueue.empty()) {
ALOGI("Not raising ANR because the connection %s has recovered",
connection->inputChannel->getName().c_str());
return;
}
/**
* The "oldestEntry" is the entry that was first sent to the application. That entry, however,
* may not be the one that caused the timeout to occur. One possibility is that window timeout
* has changed. This could cause newer entries to time out before the already dispatched
* entries. In that situation, the newest entries caused ANR. But in all likelihood, the app
* processes the events linearly. So providing information about the oldest entry seems to be
* most useful.
*/
DispatchEntry* oldestEntry = *connection->waitQueue.begin();
const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
std::string reason =
android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
connection->inputChannel->getName().c_str(),
ns2ms(currentWait),
oldestEntry->eventEntry->getDescription().c_str());
sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
//处理无响应anr
processConnectionUnresponsiveLocked(*connection, std::move(reason));
// cnnel所有事件
// Stop waking up for events on this connection, it is already unresponsive
cancelEventsForAnrLocked(connection);
}
void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
std::string reason) {
const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
std::optional<int32_t> pid;
if (connection.monitor) {
ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
reason.c_str());
pid = findMonitorPidByTokenLocked(connectionToken);
} else {
// The connection is a window
ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
reason.c_str());
const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
if (handle != nullptr) {
pid = handle->getInfo()->ownerPid;
}
}
//发送窗口无响应命令
sendWindowUnresponsiveCommandLocked(connectionToken, pid, std::move(reason));
}
//当这个command被执行时,调用的为doNotifyWindowUnresponsiveLockedInterruptible
void InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible(CommandEntry* commandEntry) {
mLock.unlock();
mPolicy->notifyWindowUnresponsive(commandEntry->connectionToken, commandEntry->reason); //调用到jni层,向java层传递
mLock.lock();
}
ANR 的通知
// Native callback
@SuppressWarnings("unused")
private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid,
String reason) {
mWindowManagerCallbacks.notifyWindowUnresponsive(token,
isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason);
}
调用java层发送弹窗命令, 就不细说了