ANR优化:从原理到实战的完整指南
ANR,全称Application Not Responding,说白了就是应用无响应。我做了这么多年系统优化,ANR可以说是最让开发头疼的问题之一。它不像崩溃那样直接闪退,而是让用户干等着,体验极差。今天我就把ANR优化的核心内容掰开揉碎了讲清楚。
ANR产生原因:到底什么情况会触发?
ANR的触发条件其实很明确。系统会监控你的主线程,如果超过一定时间没响应,就会弹出ANR对话框。具体来说:
- 输入事件超时:5秒内没处理完触摸或按键事件
- 广播超时:前台广播10秒,后台广播60秒没执行完
- 服务超时:前台服务20秒,后台服务200秒没完成
- ContentProvider超时:10秒内没发布
我在项目中遇到过最典型的场景:某个第三方SDK在主线程做了网络请求,用户快速点击了几下,ANR就出来了。你想想看,网络请求这种耗时操作放在主线程,不出问题才怪。
核心原则:主线程只做UI操作,耗时任务全部扔到子线程。
ANR日志分析:从海量日志中定位问题
当ANR发生时,系统会生成一份完整的日志。我个人的分析习惯是这样的:
- 先看traces.txt:这是最重要的文件,记录了所有线程的堆栈信息
- 定位主线程:找到"main"线程的堆栈,看它卡在哪个方法上
- 检查锁竞争:看主线程是否在等待某个锁,而锁被其他线程持有
- 分析CPU使用率:看ANR发生时CPU是否繁忙,有没有其他进程在抢资源
举个例子,我曾经分析过一个ANR日志,主线程卡在View.onDraw()里。表面上看是绘制问题,但深入一看,是因为某个自定义View在onDraw()里做了大量计算。嗯,这里要注意,onDraw()是高频调用方法,绝对不能做耗时操作。
我的小技巧:用adb pull /data/anr/traces.txt拉取日志后,直接搜索"main"关键词,快速定位主线程堆栈。
主线程耗时操作检测:把问题扼杀在摇篮里
与其等ANR发生后再分析,不如提前检测。我常用的方法有几种:
1. StrictMode严格模式
这是Android官方提供的检测工具,可以帮你发现主线程上的磁盘读写、网络访问等操作。
// 在Application的onCreate中开启
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
}
2. 自定义Looper监控
我个人更喜欢这种方式,因为它能精确统计每个消息的执行时间。
Looper.getMainLooper().setMessageLogging(new Printer() {
private static final long THRESHOLD = 2000; // 2秒阈值
@Override
public void println(String x) {
if (x.startsWith(">>>> Dispatching to")) {
startTime = System.currentTimeMillis();
} else if (x.startsWith("<<<< Finished to")) {
long cost = System.currentTimeMillis() - startTime;
if (cost > THRESHOLD) {
Log.w("ANRMonitor", "主线程耗时: " + cost + "ms");
// 这里可以上报或记录堆栈
}
}
}
});
避坑指南:我曾经在生产环境开启过StrictMode的penaltyDeath(),结果导致线上崩溃。建议只在Debug模式下使用,或者用penaltyLog()记录日志就好。
SIGQUIT信号处理:深入理解ANR的底层机制
SIGQUIT是Linux信号,编号3。当系统检测到ANR时,会向目标进程发送SIGQUIT信号。进程收到信号后,会执行以下操作:
- 暂停所有线程的执行
- 收集所有线程的堆栈信息
- 将堆栈信息写入
/data/anr/traces.txt - 恢复线程执行
为什么我要讲这个?因为理解SIGQUIT能帮你更好地分析ANR。比如,有时候你看到traces.txt里主线程卡在某个方法,但那个方法看起来并不耗时。这时候就要考虑:是不是其他线程持有了主线程需要的锁?
我记得有一次,主线程卡在SharedPreferences的getString()上。表面上看只是读个配置,但深入分析发现,另一个线程正在执行apply()操作,持有了写锁。主线程只能等待。这就是典型的锁竞争导致的ANR。
关键点:SIGQUIT信号处理是系统行为,应用层无法拦截。但我们可以通过监控/data/anr/traces.txt的变化,实现ANR的实时告警。
知识体系总览
下面这张图总结了ANR优化的核心知识结构,我建议你保存下来,遇到ANR问题时对照着排查:
实战建议:建立ANR监控体系
光知道理论还不够,我建议你在项目中建立一套完整的ANR监控体系:
| 监控层级 | 具体方案 | 优先级 |
|---|---|---|
| 预防层 | StrictMode + 代码Review | 高 |
| 检测层 | Looper监控 + 自定义Printer | 高 |
| 分析层 | traces.txt自动采集 + 堆栈解析 | 中 |
| 告警层 | 实时通知 + 聚合分析 | 中 |
最后说一句,ANR优化没有银弹。每个项目的情况都不一样,关键是要建立一套适合自己的监控和分析流程。遇到问题不要慌,按照我上面说的步骤一步步排查,大部分ANR都能找到根因。
我的经验:ANR优化是个持续的过程。我建议每个版本都做一次ANR专项治理,把新引入的耗时操作及时清理掉。时间长了,你会发现ANR率会稳定下降。
公众号:蓝海资料掘金营,微信deep3321