1. 问题初探:一个看似“玄学”的ANR
最近在测试Android 14设备时,你是不是也遇到过一种奇怪的ANR?现象是:在Launcher的“最近任务”(Recents)界面,快速启动并销毁一个Activity后,整个系统对按键输入就没了反应,过几秒就弹出一个ANR对话框,提示“Application does not have a focused window”。用Monkey测试时,这种问题尤其容易“撞上”。
我刚开始排查时也是一头雾水,因为从logcat看,系统服务(System Server)和Launcher进程本身似乎都没卡死,CPU占用也不高。但ANR就是发生了,而且复现步骤出奇地简单和固定。这背后其实是一个Android 14系统层,特别是窗口管理系统(WindowManagerService, WMS) 在处理特定窗口焦点切换时序时留下的逻辑漏洞。简单来说,就是系统在某个瞬间“弄丢”了应该接收用户输入的焦点窗口,导致后续所有输入事件无人接管,最终超时触发ANR。
这个问题不仅影响Launcher,任何在Recents界面下进行快速Activity跳转的应用都可能中招。对于应用开发者,它表现为难以解释的ANR;对于系统开发者,它则是WMS、InputDispatcher(输入分发器)和Activity生命周期协同工作中的一个“坑”。今天,我就带你深入这个问题的核心,从复现、分析到修复,一步步把它讲透。即使你不是系统底层专家,也能通过这篇文章理解问题的来龙去脉,并掌握排查类似窗口焦点问题的基本思路。
2. 亲手复现:让“幽灵”ANR现出原形
分析问题最好的方式就是先能稳定复现。根据log分析,这个ANR的核心场景可以精炼为:在Recents界面,一个Activity被启动后极速销毁,导致窗口焦点丢失。下面是我验证过的、可稳定复现的步骤,你可以跟着操作一遍,感受一下这个“Bug”的触发条件。
2.1 复现环境与工具准备
首先,你需要一台运行Android 14的设备(真机或模拟器均可),并开启开发者选项和USB调试。我们将通过ADB命令来模拟用户操作,这比手动操作更精确。关键工具就是adb shell input命令,它可以模拟按键和触摸事件。
2.2 四步复现操作法
完整的复现流程分为四步,我写了一个简单的Demo应用来配合:
- 启动一个普通Activity:我们称之为
MainActivity。通过adb shell am start命令启动它。此时,这个Activity拥有焦点。 - 触发Recents界面:输入
KEYCODE_RECENT_APPS事件(键码312)。这相当于按了导航栏的“最近任务”键,屏幕会切换到Launcher的Recents界面,显示所有最近使用的应用缩略图。 - 快速启动并销毁另一个Activity:这是关键一步。在切换到Recents界面后极短时间(如100毫秒)内,让
MainActivity以NEW_TASK的方式启动另一个Activity,比如叫SingleTaskActivity。并且,在这个SingleTaskActivity的onCreate()方法里,立刻调用finish()。这样,这个Activity就像“闪现”了一下,瞬间创建又瞬间销毁。 - 触发ANR检测:此时,系统已经处于“无焦点窗口”状态。再输入任何一个按键事件(比如
KEYCODE_BUTTON_C,键码98),等待大约5秒,ANR对话框就会弹出。
为什么是这四步? 第一步和第二步创造了Recents界面这个上下文环境。第三步的“快速启动又销毁”制造了一个时间窗口:当SingleTaskActivity销毁时,系统需要将焦点交还给上一个窗口(理应是Launcher的Recents界面),但由于Recents界面本身的特殊性和快速销毁带来的生命周期交错,WMS更新焦点的逻辑在这里出现了判断失误,导致没有窗口被成功设为焦点。第四步的按键,就像扣动了扳机,让InputDispatcher发现“没有窗口能处理输入”,从而启动ANR倒计时。
我最初在自定义ROM的设备上复现了这个问题,但有趣的是,在Google Pixel设备上使用相同的步骤和APK,却发现无法复现。这强烈暗示,这可能是AOSP原生代码的一个缺陷,而Pixel设备可能已经合入了尚未公开的补丁。这个差异也为我们后续定位根因


3752

被折叠的 条评论
为什么被折叠?



