Android和焦点相关的ANR
在android设备上和焦点相关的anr我认为有两种
- no focus anr,这是一种常见的anr,简单来讲是由 分发key事件时找不到对应屏幕上的焦点窗口引起.
- FocusEvent 处理超时anr,这个后面后详细讲讲.
一. no focus anr
1. 埋雷阶段
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
.....
// Identify targets.
std::vector<InputTarget> inputTargets;
// 寻找该事件应该被派发的窗口,一般对应屏幕上的焦点窗口( no focus anr的雷就在该阶段埋下)
InputEventInjectionResult injectionResult =
findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
}
setInjectionResult(*entry, injectionResult);
if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
return true;
}
.....
// Dispatch the key. 正式开始分发该key事件
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
1.获取该屏幕上对应的焦点窗口,获取该屏幕上对应的焦点应用(对应ActivityRecord)
2.如果焦点窗口为null且焦点应用也为null,则丢弃该事件,同时返回InputEventInjectionResult::FAILED
3.如果焦点窗口为null,但焦点应用不为null,就开始做no focus anr的检查了,如果mNoFocusedWindowTimeoutTime没有值,则说明之前没有开始无焦点的anr的触发计时,如果mNoFocusedWindowTimeoutTime没有值,则说明之前没有开始无焦点的anr的触发计时,获取焦点应用配置的时间分发超时的时长,设置触发anr的时间 = 当前时间 + 焦点应用配置的时间分发超时时间,设置inputDispatchar自己唤起的时间为触发该次无焦点anr的时间,埋下 no focus anr的雷
4. 如果一切正常就重置无焦点anr的计时
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime) {
std::string reason;
// 获取该key事件对应的屏幕id
int32_t displayId = getTargetDisplayId(entry);
// 获取该屏幕上对应的焦点窗口
sp<InputWindowHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
// 获取该屏幕上对应的焦点应用(对应ActivityRecord)
std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// 如果焦点窗口为null且焦点应用也为null,则丢弃该事件,同时返回InputEventInjectionResult::FAILED
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
NamedEnum::string(entry.type).c_str(), displayId);
return InputEventInjectionResult::FAILED;
}
// 如果焦点窗口为null,且焦点应用不为null
if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
// 如果mNoFocusedWindowTimeoutTime没有值,则说明之前没有开始无焦点的anr的触发计时
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);
// 设置触发anr的时间 = 当前时间 + 焦点应用配置的时间分发超时时间
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));
// 设置inputDispatchar自己唤起的时间为触发该次无焦点anr的时间
*nextWakeupTime = *mNoFocusedWindowTimeoutTime;
// 返回InputEventInjectionResult::PENDING
return InputEventInjectionResult::PENDING;
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
NamedEnum::string(entry.type).c_str());
return InputEventInjectionResult::FAILED;
} else {
// Still waiting for the focused window
return InputEventInjectionResult::PENDING;
}
}
// 一切ok,重置无焦点anr的计时
// we have a valid, non-null focused window
resetNoFocusedWindowTimeoutLocked();
// Check permissions.
if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
return InputEventInjectionResult::PERMISSION_DENIED;
}
if (focusedWindowHandle->getInfo()->paused) {
ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
return InputEventInjectionResult::PENDING;
}
if (entry.type == EventEntry::Type::KEY) {
if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
*nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
return InputEventInjectionResult::PENDING;
}
}
// Success! Output targets.
addWindowTargetLocked(focusedWindowHandle,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0), inputTargets);
// Done.
// 一切ok ,返回InputEventInjectionResult::SUCCEEDED;
return InputEventInjectionResult::SUCCEEDED;
}
2. no focus anr的触发阶段
如上,当分发keyevent时,当focusedApplicationHandle不为null,且focusedWindowHandle为null时,就设置了一个无焦点的anr触发时间。
接下来就分析该类型的anr如何真正触发的:
在事件的分发函数dispatchOnce中会对anr进行处理
```cpp
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
// 先把下次的唤醒事件是设置为无穷大
nsecs_t nextWakeupTime = LONG_LONG_MAX;
// 在当前版本上 一个时间处理时间 超时引起的anr 不用等下一个时间来触发
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// 分发事件
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 (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// 处理anr 不仅仅处理了anr, 还会 算出 最近一次anr唤醒的超时时间,时间到了 就唤醒.
// 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 == LONG_LONG_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);
}
如下:
1.mNoFocusedWindowTimeoutTime 已经有值,说明之前有埋下 no focus anr的雷,那就做相关的检查了
2.如果当前的时间大于之前设置的anr触发的时间, 则触发无焦点类型的anr,随后上报无焦点anr,重置相关值
3.如果没有超过,则 nextAnrCheck = *mNoFocusedWindowTimeoutTime,这一轮里面不触发无焦点anr,nextAnrCheck为下一次唤醒时间
/**
* Check if any of the connections' wait queues have events that are too old.
* If we waited for events to be ack'ed for more than the window timeout, raise an ANR.
* Return the time at which we should wake up next.
*/
nsecs_t InputDispatcher::processAnrsLocked() {
const nsecs_t currentTime = now();
nsecs_t nextAnrCheck = LONG_LONG_MAX;
// Check if we are waiting for a focused window to appear. Raise ANR if waited too long
// 上文中 mNoFocusedWindowTimeoutTime 已经有值
if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
// 如果当前的时间大于之前设置的anr触发的时间, 则触发无焦点类型的anr
if (currentTime >= *mNoFocusedWindowTimeoutTime) {
// 上报无焦点anr
processNoFocusedWindowAnrLocked();
// 重置相关值
mAwaitedFocusedApplication.reset();
mNoFocusedWindowTimeoutTime = std::nullopt;
return LONG_LONG_MIN;
} else {
// 如果没有超过,则 nextAnrCheck = *mNoFocusedWindowTimeoutTime,这一轮里面不触发无焦点anr
// Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
nextAnrCheck = *mNoFocusedWindowTimeoutTime;
}
}
// 下来讲会处理分发给窗口的事件没有及时处理的anr
// 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.
sp<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);
return LONG_LONG_MIN;
}
上报无焦点anr:
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
/**
* Raise ANR if there is no focused window.
* Before the ANR is raised, do a final state check:
* 1. The currently focused application must be the same one we are waiting for.
* 2. Ensure we still don't have a focused window.
*/
void InputDispatcher::processNoFocusedWindowAnrLocked() {
// Check if the application that we are waiting for is still focused.
std::shared_ptr<InputApplicationHandle> focusedApplication =
getValueByKey(mFocusedApplicationHandlesByDisplay, mAwaitedApplicationDisplayId);
// 如果期间的焦点app发生了变化,则放弃上报此次的无焦点anr
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.
}
// 检查现在的焦点窗口是否存在,存在则放弃此次无焦点anr的上报
const sp<InputWindowHandle>& focusedWindowHandle =
getFocusedWindowHandleLocked(mAwaitedApplicationDisplayId);
if (focusedWindowHandle != nullptr) {
return; // We now have a focused window. No need for ANR.
}
// 触发anr
onAnrLocked(mAwaitedFocusedApplication);
}
随后,该anr将抛给了上层来处理。
二、FocusEvent 处理超时anr
除此之外,还有一种看起来和焦点窗口相关的的anr,常见的打印如下:
Subject: Input dispatching timed out (fd239fd lenovo.vcl.crashdemo/lenovo.vcl.crashdemo.MainActivity (server) is not responding. Waited 5001ms for FocusEvent(hasFocus=false))
最重要的打印是····for FocusEvent(hasFocus=false))
,该log的打印虽然包含Focus字段,但实际上它不是无焦点anr,上一篇文章分析节点切换流程的时候,提到过当inputdispatcaher在切换焦点是分别会给焦点进入的窗口和焦点切出的窗口发送一个Focusevent。该事件想普通的时间一样,处理完需要再和inputdispatcaher,辨明该事件已经处理完毕,否则,5s过后,inputdispatcaher分发时间再次唤醒时,触发一个分发超时的anr。
再回到贴出的log,很明显这是一个焦点撤出的时间处理超时的anr
这种anr一般发生在应用的关闭阶段,处理这种anr重点关注关闭的activity的onPaused 或者 onstopped函数中有没有用耗时的操作,同时结合anr的堆栈打印进行分析。