当前位置: 首页 > news >正文

Android14 InputManager-ANR原理

目标窗口查找时,作为派发目标的窗口必须已经准备好接收新的输入事件,否则判定窗口处于未响应状态,终止事件的派发过程,并在一段时间后再试。倘若5s后窗口仍然未准备好接收输入事件,将导致ANR。直接引发ANR的原因有很多,

例如Activity生命周期函数调用超时,

服务启动超时

以及最常见的输入事件处理超时等。

  1. Service ANR:前台20s,后台200s;startForeground超时10s
  2. Broadcast ANR:前台10s,后台60s
  3. Input ANR:按键或触摸事件在5s内无响应
  4. ContentProvider ANR:10s,少见

下面从输入事件超时的角度讨论ANR的产生原因与过程

inputANR 分为两种

无响应anr: 应用连接正常但是未相应事件,并且发生了超时

无焦点窗口anr: 当前有焦点应用,但是无焦点窗口,并且超时

void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LLONG_MAX;{ // acquire lockstd::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 eventsif (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 longif (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {if (currentTime >= *mNoFocusedWindowTimeoutTime) {processNoFocusedWindowAnrLocked();// 无焦点anrmAwaitedFocusedApplication.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 duenextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());if (currentTime < nextAnrCheck) { // most likely scenarioreturn 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 connectionmAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());onAnrLocked(connection);// 无响应anrreturn 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 resultint32_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 featureif (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 timerstd::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 eventALOGE("Dropping %s event because there is no focused window",ftl::enum_string(entry.type).c_str());return nullptr;} else {// Still waiting for the focused windowoutInjectionResult = InputEventInjectionResult::PENDING;return nullptr;}}// we have a valid, non-null focused windowresetNoFocusedWindowTimeoutLocked();// 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 changedALOGE("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 situationif (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 unresponsivecancelEventsForAnrLocked(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 windowALOGW("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层发送弹窗命令, 就不细说了

相关文章:

  • 华清远见作业第四十一天——Qt(第三天)
  • Redis高性能原理
  • 掌握Docker:让你的应用轻松部署和管理
  • 使用openai-whisper实现语音转文字
  • 外汇天眼:外汇交易不可不知的8大风险!
  • 欧放ER-2024年1月 AI论文速递
  • Draw.io绘制UML图教程
  • vue3新特性-defineOptions和defineModel
  • 2024 Sora来了!“手机Agent智能体”也来了!
  • MyBatis Plus:自定义typeHandler类型处理器
  • Autodesk CAD如何建立图层方框?
  • Python学习笔记——PySide6设计GUI应用之UI与逻辑分离
  • git中将所有修改的文件上传到暂存区
  • 探索最新AI视频生成技术——OpenAI Sora模型的机遇和挑战
  • websoket
  • 【css3】浏览器内核及其兼容性
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • CentOS6 编译安装 redis-3.2.3
  • ES6 ...操作符
  • ES学习笔记(12)--Symbol
  • exports和module.exports
  • FineReport中如何实现自动滚屏效果
  • gitlab-ci配置详解(一)
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • Python 反序列化安全问题(二)
  • React Transition Group -- Transition 组件
  • React 快速上手 - 07 前端路由 react-router
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 深度学习在携程攻略社区的应用
  • 我有几个粽子,和一个故事
  • 系统认识JavaScript正则表达式
  • 用Canvas画一棵二叉树
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 数据库巡检项
  • ​configparser --- 配置文件解析器​
  • #include
  • #pragma预处理命令
  • #宝哥教你#查看jquery绑定的事件函数
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (十六)一篇文章学会Java的常用API
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)关于多人操作数据的处理策略
  • .bat批处理(一):@echo off
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET Framework与.NET Framework SDK有什么不同?
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .net 程序发生了一个不可捕获的异常