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

Android焦点窗口变化导致遥控键值监听失效问题分析

最近在做语音全局控制Android系统功能,通过集成第三方语音识别sdk得到相关控制指令,然后将指令通过进程间通信传递给当前应用并作出响应。

有很多通用指令,比如播放/暂停,Android系统本身就有全局控制指令:KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,只需模拟发送该遥控键值即可,如下:

 Instrumentation inst = new Instrumentation();// 调用inst对象的按键模拟方法inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);

其他控制指令也可以类似处理,模拟发送一个遥控键值,然后在目标应用中通过监听键值事件进行功能处理即可:

    @Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode){case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:break;}return super.onKeyDown(keyCode, event);}

正常这个逻辑是没问题的,但实际使用中发现,只要是通过语音指令模拟发送的键值,目标应用都无法监听到,而通过遥控器直接操作或者adb命令操作都没问题,这个就很奇怪了???

后面通过log分析看到当前KeyEvent接收的应用包名是上面集成的第三方语音服务的,这就清楚了,语音交互的时候有个弹窗,遥控事件被弹窗拦截了。

解决办法有个方案:

1.延时发送模拟键值,等弹窗消失后再发送

2.通过发送广播方式进行通信,目标应用接收广播后处理。

下面给出一份从应用侧到系统侧的「Android 焦点窗口变化」完整链路梳理,覆盖触发时机、WMS/SurfaceFlinger/InputDispatcher 三大核心模块的协作过程,并补充常被忽略的细节(多屏、IME、无焦点 ANR 等)。


一、名词澄清
• 焦点窗口(Focused Window):InputDispatcher 在派发 KeyEvent 时唯一的目标窗口,与 View 体系中“焦点 View”不是一个层次的概念。
• TopFocusedDisplay:RootWindowContainer.mTopFocusedDisplayId,决定了当 KeyEvent 未指定屏幕时由哪块屏接收。


二、触发场景

  1. 窗口增删/可见性变化:relayoutWindow()、removeWindow()、finishDrawingWindow()。

  2. 切换 Activity/Dialog 弹出。

  3. 多屏切换:setFocusedDisplay()。

  4. 强制清除焦点:turnScreenOff(), 锁屏。

  5. 输入法窗口显隐:InputMethodManagerService 调用 WMS.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES)。


三、核心流程(一次典型的焦点切换)

阶段 1:WMS — “选拔”焦点窗口
① 入口:WMS.updateFocusedWindowLocked(mode, updateInputWindows)
② RootWindowContainer 遍历 DisplayContent,调用 dc.updateFocusedWindowLocked()
③ DisplayContent.findFocusedWindowIfNeeded() 按 Z-order 自上而下过滤:
• 可见、非悬浮错误类型、非 SYSTEM_OVERLAY、不被遮挡
• 若存在焦点 Activity(mAppToken.containsFocusedWindow),则返回其主窗口
• 若无 Activity 状态正常,则选顶部可聚焦窗口
④ 若 newFocus==mCurrentFocus,直接返回;否则把 mCurrentFocus 更新为 newFocus 并记录日志。
⑤ getInputMonitor().setInputFocusLw(newFocus, updateInputWindows) 把结果同步给 InputDispatcher。
⑥ 其他副作用:
• 调整 IME target、toast 超时、Task 阴影、SystemUI 可见性。

阶段 2:SurfaceFlinger — 仅做事务封装
• InputMonitor 通过 SurfaceFlinger 的 createInputWindow/Transaction 把 InputWindowHandle 传给 InputDispatcher。
• 新版本使用 gui::WindowInfosUpdate 批量传输,减少 Binder 次数。

阶段 3:InputDispatcher — “选举结果”生效
① onWindowInfosChanged() → setInputWindowsLocked() 更新 mWindowHandlesByDisplay。
② setFocusedWindow():
• FocusResolver.updateFocusedWindow() 返回 FocusChanges{oldFocus, newFocus, displayId, reason}。
• onFocusChangedLocked():
- 若 oldFocus 存在,向旧窗口发送 CANCEL_NON_POINTER_EVENTS,并 enqueueFocusEvent(oldToken, false)。
- 向新窗口 enqueueFocusEvent(newToken, true)。
• mLooper->wake() 触发下一次 pollOnce,立即派发 FocusEvent。

阶段 4:应用进程 — 收到焦点变更
• Java ViewRootImpl 收到 FocusEvent → DecorView.onWindowFocusChanged() → Activity.onWindowFocusChanged() → 开发者可重写。
• UnityPlayer、FlutterEngine 等 Native 引擎通过监听 windowFocusChanged 决定是否继续渲染或暂停音频。


四、无焦点 ANR 的形成
• InputDispatcher 在派发 KeyEvent 时若 mFocusedWindowTokenByDisplay[displayId] 为空且 5 s 内仍无窗口获得焦点,触发 “Reason: Waiting because no window has focus …” ANR。


五、多屏场景补充
• 每块 DisplayContent 维护独立的 mCurrentFocus。
• RootWindowContainer.updateTopFocusedDisplay() 在每次焦点窗口变化后重新计算 mTopFocusedDisplayId:
– 若某屏有 Activity 含有顶部可见窗口 → 选该屏;
– 否则选默认内屏。


六、关键类/文件速查
WMS:WindowManagerService.java、RootWindowContainer.java、DisplayContent.java、InputMonitor.java
Input:InputDispatcher.cpp、FocusResolver.cpp、InputWindowInfo.h
SurfaceFlinger:SurfaceFlinger.cpp(createInputWindow、Transaction)


七、一句话总结
“焦点窗口”的更新是一场跨进程接力:
WMS 负责“选拔” → SurfaceFlinger 负责“运输” → InputDispatcher 负责“生效”,
最终让正确的窗口在 KeyEvent 到达时“独占”输入事件。

http://www.xdnf.cn/news/18575.html

相关文章:

  • # 重磅发布 | onecode 3.0.1 Base 源码正式开源:AI赋能的企业级开发框架
  • XXL-Job REST API 工具类完全解析:简化分布式任务调度集成
  • (第二十期上)HTML 超链接标签 a
  • 【python与生活】如何从视频中提取关键帧?
  • FPGA DP1.4 With DSC解决方案
  • 【华为OD-C卷-019 对称字符串 100分(python、java、c++、js、c)】
  • Vitest 测试框架完全指南 – 极速单元测试解决方案
  • C++ 常见的排序算法详解
  • AI 产业落地:从 “实验室神话” 到 “车间烟火气” 的跨越
  • Spring Cloud Netflix学习笔记06-Zuul
  • 机器学习中的集成算法与 k 均值聚类算法概述
  • uniapp跨域怎么解决
  • Go 并发编程-channel
  • 详解开源关键信息提取方案PP-ChatOCRv4的设计与实现
  • AI客服系统架构与实现:大模型、知识库与多轮对话的最佳实践
  • Android为ijkplayer设置音频发音类型usage
  • 【C2000常见问题】JTAG仿真器类型和JTAG Debug定位方法
  • 机器学习核心算法笔记:集成学习与聚类算法
  • springboot人事管理系统源码和论文
  • c#语言的学习【02,函数重载】
  • GPT5 / 深度研究功能 无法触发
  • 网络流量分析——基础知识(二)(Tcpdump 基础知识)
  • HTTP/2 性能提升的核心原因
  • 笔记本电脑Windows+Ubuntu 双系统,Ubuntu无法挂载Windows的硬盘 报错问题解决
  • nginx-重定向-正则表达式-路由匹配优先级
  • 最新react,vue 解决无法使用js触发点击,解决方案
  • SamOutVXP: 轻量级高效语言模型
  • 通信工程学习:什么是Camera Calibration相机标定
  • WaitForSingleObject函数详解
  • python测试开发django-1.开始hello world!