ANR
ANR(Application Not Responding)是 Android 检测到应用没有在规定期限内完成某类响应或组件处理的异常状态。它关注的是系统等待的工作是否及时完成,不只是“某个函数运行得比较慢”。排查时先确定超时类型,再沿执行与等待关系寻找原因,不能看到主线程栈顶就立即下结论。
1. 面试核心
先记住这几条,再读正文- 掉帧、ANR 和崩溃是不同现象,ANR 由特定响应期限触发,不能把所有卡顿都称为 ANR。卡 · 卡顿、ANR 与崩溃
- 输入、广播、Service 等有不同检测条件,不存在覆盖所有 ANR 的统一“主线程 5 秒规则”。卡 · 先读超时原因
- 主线程可能在计算,也可能等锁、I/O 或 Binder,把实际工作移到后台后再在主线程同步等待,仍然会阻塞响应。卡 · 执行链与等待链
- ANR 排查需要类型、时间、全线程栈和系统调度证据,真正根因可能在锁持有线程、远端进程或迟迟未获调度的工作中。卡 · 现场证据与定位
nativePollOnce通常意味着线程在等消息,既不能直接把它当作根因,也不能据此忽略其他线程或已经结束的超时阶段。卡 · nativePollOnce 的解释goAsync()延后广播完成时点,不会取消广播执行期限,仍需及时调用PendingResult.finish()。卡 · 广播异步处理的期限- StrictMode 和压力测试能提前暴露部分问题,验证应覆盖原来的等待链和真实设备条件,而不只看一次没有弹出对话框。卡 · 预防与修复验证
2. 响应期限与检测类型
2.1 卡顿、ANR 与崩溃
卡顿表示用户看到的帧或交互没有按预期及时完成;ANR 表示系统等待的某项工作已经超过对应期限;崩溃则通常意味着未处理异常、非法内存访问等原因使进程异常终止。
一次主线程长任务可能先造成多帧卡顿,持续阻塞后再触发输入 ANR,但两者没有固定的一一对应关系。一个后台广播没有按时结束,即使主线程此刻还能绘制,也可能出现广播 ANR。
ANR 不是在某一行普通业务代码抛出、由外层 try/catch 捕获后就能消除的 Java 异常。系统是否显示对话框、报告怎样采集,还取决于类型、前后台状态和平台行为,不能只用“用户有没有看到弹框”判断是否发生过 ANR。
2.2 先读超时原因
| 类型或线索 | 系统主要在等什么 | 优先排查什么 |
|---|---|---|
| Input dispatch timeout | 输入得到应用响应 | 主线程工作、锁、同步调用、窗口是否及时就绪 |
| Broadcast receiver timeout | 广播处理完成 | onReceive() 所在线程、异步任务、finish()、应用冷启动 |
| Executing service timeout | Service 生命周期相关执行完成 | 主线程前序工作、组件初始化、同步 I/O 或依赖调用 |
| Content provider not responding | 相应 Provider 请求及时完成 | Provider 初始化、执行请求的 Binder 线程和依赖资源 |
AOSP 和 Pixel 的输入分发默认超时通常为 5 秒,设备实现可能调整。这个数值不能套给所有组件,也不能解释为“主线程任何时候运行超过 5 秒就必然触发同一种 ANR”。例如没有可接收输入的焦点窗口,也需要检查窗口创建和配置,而不仅是业务方法耗时。
广播期限还与 Intent 是否带有 FLAG_RECEIVER_FOREGROUND 等条件和平台版本有关,不应把它简单等同于“应用前台或后台”。Android 14 的相应机制还会考虑进程是否缺少 CPU 执行机会。面试需要准确说明计时对象与条件;现场定位应以实际系统报告和版本为准。
3. 执行链与等待链
3.1 主线程忙与主线程等待
主线程承担输入分发、界面更新和大量组件回调。某项任务长时间占住它,后面的任务只能等待。但长时间占用不一定表现为 CPU 使用率高。
| 栈或 trace 线索 | 可能含义 | 后续需要的证据 |
|---|---|---|
| 持续出现在业务计算或大型解析 | CPU 工作过多 | 实际运行时间、热点、输入规模 |
| 等待 monitor 或锁 | 其他线程没有释放资源 | 锁持有者、持锁范围及其后续等待 |
| 文件或数据库调用中停留 | 同步 I/O 或数据库竞争 | I/O 耗时、事务、锁与存储状况 |
| 同步 Binder 调用等待返回 | 远端处理慢或远端也在等待 | 对端线程、服务负载与调用次数 |
| 处于 runnable 却长期没有运行 | 有资格执行,但没有获得足够 CPU | 调度轨迹、系统负载与线程竞争 |
例如主线程为了读取配置取得 configLock,后台线程却持有该锁执行网络请求。表面上主线程只是一个很短的读取,实际响应时间取决于网络请求何时结束。修复应缩小持锁范围或改变数据交付方式,不能仅优化读取方法本身。
3.2 异步必须沿调用链成立
把请求交给线程池后立即在主线程执行 Future.get()、join() 或等待锁,主线程仍然需要等结果。工作位置改变了,等待依赖没有改变。
同样,suspend 只描述可挂起的调用形式,不保证函数内部所有代码都在后台线程执行。生命周期协程通常从主线程开始,阻塞 I/O 和大量计算需要明确放到合适的执行上下文;结果准备好后再异步更新 UI。
同步 Binder 调用也要沿依赖检查。应用主线程等待远端服务,远端服务又等待应用回调,可能形成互相等待。只看应用这一侧的一个栈,无法解释整个链路,参见 Binder。
3.3 广播异步处理的期限
BroadcastReceiver.onReceive() 默认在主线程执行;动态注册时可以指定其他 Handler。同步处理的完成时点通常是回调返回,调用 goAsync() 后则把完成责任交给返回的 PendingResult。
异步路径必须在所有退出分支及时执行 finish(),通常用可靠的清理结构保证。但即使 finally 中写了 finish(),任务长时间排在线程池队列里还没开始,也仍然可能超时。
这说明广播 ANR 不一定是主线程正在执行慢方法:可能是工作线程池已被其他任务占满,也可能是异步 I/O 没有结束。goAsync() 提供短暂异步处理能力,不是无限后台执行许可;可延后的长任务应交给合适的 Android 后台任务 机制。
4. 现场证据与定位
4.1 保留同一次事件的上下文
先确定一次事件的包名、进程、版本、发生时间、用户操作和系统超时描述。再把线程栈、日志和 trace 对齐到这一段时间,避免把重启后的空闲栈解释成故障当时的状态。
可在已连接并授权调试的设备上采集 bugreport:
adb bugreport
报告包含系统日志、线程栈等诊断信息,具体内容和访问能力受设备与系统版本影响。它不是只包含某一条 ANR 的小文件;查找时应对应事件时间、进程和组件,分享前处理无关的设备与用户信息。
线上报告还需要对应发布构建及符号信息。混淆后的 Java/Kotlin 栈应使用匹配构建的 mapping 还原,native 栈需要匹配二进制和符号;错误版本的符号文件会把定位带到无关代码。
4.2 从受阻线程追到持有者
线程栈显示的是采样时正在执行或等待的位置,不自动告诉你前面发生了多久。主线程若等待锁,沿对象标识和线程信息找持锁线程,再继续看它是在计算、I/O、等待另一个锁,还是同步调用远端。
Perfetto 等系统 trace 可以补充时间关系:线程何时被唤醒、何时真正运行、某个同步 Binder 调用占了多久,以及其他线程和系统进程是否同时拥塞。这样才能区分“应用做了太多工作”和“工作具备执行条件但没有被调度”。
没有完整 trace 时,要把结论分成已知与待验证。比如“主线程当时等待某个锁”可以由栈支持;“后台网络请求持锁 8 秒”则需要持锁线程、日志或复现进一步确认,不能凭一个方法名猜出时长。
4.3 nativePollOnce 的解释
MessageQueue.nativePollOnce() 是消息循环等待工作的路径,正常空闲时也会出现,见 Android 消息机制。它本身不能证明主线程执行了一个有问题的死循环。
ANR 报告出现这个栈,可能因为堆栈采集偏晚,主线程已经恢复空闲;也可能因为真正超时的是后台广播或 Provider 的工作线程。应回到超时类型检查相关线程和时间,而不是一律忽略该报告。
如果 UI 线程约束被破坏,还应检查 View 层级修改等问题是否影响消息处理。只有取得具体证据后,才进一步分析同步屏障或平台实现;不能看到 nativePollOnce 就直接断言“同步屏障泄漏”。
5. 预防与修复验证
5.1 开发阶段暴露阻塞操作
StrictMode 可以在开发阶段检测部分不合适的线程操作,例如主线程磁盘和网络访问。下面的函数应在调试构建的主线程初始化时调用:
import android.os.StrictMode
fun enableDebugThreadChecks(debugBuild: Boolean) {
if (!debugBuild) return
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}
这里设置的是调用线程的策略,示例记录问题而不直接终止进程。它不能替代完整 ANR 检测:锁等待、CPU 计算、某些 native 行为与调度问题不一定由这些检查覆盖。
5.2 修复必须改变阻塞关系
不同原因需要不同修复:主线程大量计算应减少工作或移到后台;锁竞争应缩小临界区并避免持锁 I/O;线程池饱和要检查排队、并发预算和任务分类;慢 Binder 调用要减少同步依赖或改为合适的异步交付。
不要用无限增加线程数处理所有等待。更多线程可能增加竞争和内存,甚至加剧系统负载。任务拆分后,也要保留错误、取消、顺序和过期结果处理。
验证时重复原操作,覆盖冷启动、较大数据量、慢存储、弱网和并发等与故障有关的条件。对照主线程占用、锁等待、排队与任务完成时间,确认原来的超时链已经缩短或消除;同时检查新实现没有破坏功能。
开发机上没有弹出一次 ANR 对话框,只是有限证据。线上还需要观察相同构建范围内的事件类型、发生率和影响场景,避免不同版本或用户群混在一起造成错误结论。
6 · 面试题卡片
本页题目与参考回答
6.1 ANR、卡顿和崩溃有什么区别?
考察重点:用户体验、系统响应期限和进程终止的区别。
相关内容:卡顿、ANR 与崩溃。
参考回答
卡顿是帧或交互延迟,ANR 是系统等待的某项应用处理超过期限,崩溃通常是异常或非法访问导致进程异常终止。 长主线程任务可能先卡顿再触发输入 ANR,但不能把所有卡顿都等同于 ANR。
后台组件超时也可能产生 ANR,且不一定总以同样的对话框呈现。
6.2 ANR 是否都由主线程阻塞 5 秒触发?
考察重点:检测类型、计时对象和版本条件。
相关内容:先读超时原因。
参考回答
不是。5 秒是 AOSP/Pixel 输入分发默认超时的典型值,不是所有 ANR 的统一规则。 广播、Service 和 Provider 等具有不同完成条件及期限,设备实现也可能不同。
广播还要区分优先级标记与平台版本,不能只按应用是否在前台套数值。排查先读报告中的超时原因,再选择相关线程与执行链。
6.3 工作已放到子线程,为什么仍可能发生 ANR?
考察重点:工作执行位置与同步等待依赖。
相关内容:执行链与等待链。
参考回答
子线程执行不代表主线程不再等待。 主线程调用 Future.get()、join() 或等待子线程持有的锁,仍然无法及时响应。
另外,后台广播任务自身也有完成期限。应该沿等待关系检查锁持有者、线程池排队、同步 Binder 与任务完成责任,而不只确认创建过子线程。
6.4 怎样排查 ANR?
考察重点:事件类型、现场关联、线程与系统证据。
相关内容:现场证据与定位、修复必须改变阻塞关系。
参考回答
先确认超时类型、时间、版本和用户操作,再结合全线程栈、日志和 trace 追踪受阻工作。 主线程计算就查热点;等锁就找持锁线程;同步 Binder 慢就继续查对端;runnable 长时间没运行则看调度与系统负载。
修改后用同一场景和相关压力条件对照等待时间与完成情况,同时验证功能。不能只凭栈顶方法名,或一次没有弹框就判断修复完成。
6.5 ANR 栈顶是 nativePollOnce,能得出什么结论?
考察重点:采样时刻、消息循环与相关线程。
相关内容:nativePollOnce 的解释。
参考回答
它通常说明采样时线程在等待消息,不能单独认定这是 ANR 根因。 可能采样偏晚,也可能超时责任在广播工作线程或其他组件中。
应重新对齐事件时间和类型,检查相关线程与系统 trace。它既不是“死循环导致 ANR”的直接证据,也不是可以忽略报告的充分理由。
6.6 goAsync 能让广播无限期在后台处理吗?
考察重点:广播完成责任、线程池排队与超时。
相关内容:广播异步处理的期限。
参考回答
不能。goAsync() 把完成时点延后到 PendingResult.finish(),仍需在相应期限内结束。 忘记调用、异步 I/O 卡住,或者工作在线程池中长期排队,都可能超时。
需要覆盖所有退出分支并尽快完成;长时间可延后任务应交给合适的持久任务机制,而不占用广播处理窗口。
6.7 StrictMode 能证明应用不会发生 ANR 吗?
考察重点:检测能力与修复证据的边界。
相关内容:预防与修复验证。
参考回答
不能。StrictMode 能暴露配置范围内的部分不当操作,但不覆盖全部锁、计算、调度和 native 问题。 没有日志不等于不存在 ANR 风险。
还需对关键场景做 trace、压力复现和发布后的事件观察,验证实际等待链与任务完成时间。
7. 参考资料
- Android Developers:ANR
- Android Developers:诊断与修复 ANR
- Android Developers:进程与线程
- Android Developers:BroadcastReceiver
- Android Developers:Service
- Android Developers:StrictMode
- Android Developers:采集与阅读 bugreport
- Perfetto:调度分析
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明