Android中ANR是什么,如何定位和解决ANR问题?
ANR是什么?
ANR(Application Not Responding)是Android系统的一种保护机制。当应用主线程在规定时间内无法响应用户操作或系统事件时,系统会弹出"应用无响应"对话框,让用户选择继续等待或强制关闭。
ANR不是崩溃(Crash),二者本质不同:Crash是程序异常导致的进程终止,ANR是主线程阻塞导致的超时告警。一个Crash的进程也可能同时触发ANR——如果主线程在异常处理过程中阻塞了输入事件分发。
ANR的触发条件
| 类型 | 超时阈值 | 触发场景 |
|---|---|---|
| 输入事件ANR | 5秒 | 按键/触摸事件未在窗口内完成分发 |
| 前台广播ANR | 10秒 | onReceive()执行超时 |
| 后台广播ANR | 60秒 | 后台广播接收器超时 |
| 前台Service ANR | 20秒 | Service生命周期方法执行超时 |
| 后台Service ANR | 200秒 | 后台Service超时 |
| ContentProvider ANR | 10秒 | ContentProvider操作未及时返回 |
ANR的常见原因
主线程执行耗时操作
这是ANR最常见的原因。网络请求、数据库查询、文件IO、复杂计算等操作如果在主线程执行,会阻塞事件分发,触发ANR。
kotlin// 错误示例:主线程网络请求 fun onClick(v: View) { val result = httpClient.execute(request) // 直接在主线程网络请求,5秒超时必ANR } // 正确示例:协程切换到IO线程 lifecycleScope.launch { val result = withContext(Dispatchers.IO) { httpClient.execute(request) } // 回到主线程更新UI textView.text = result }
死锁
主线程等待子线程持有的锁,子线程又等待主线程持有的锁,形成循环等待。
kotlinprivate val lock1 = Object() private val lock2 = Object() // 主线程 synchronized(lock1) { Thread.sleep(100) synchronized(lock2) { /* 死锁 */ } } // 子线程 synchronized(lock2) { Thread.sleep(100) synchronized(lock1) { /* 死锁 */ } }
Binder通信超时
跨进程调用时,如果服务端进程无响应或响应过慢,客户端主线程会因等待Binder回调而阻塞。
内存不足导致频繁GC
内存紧张时,系统频繁触发GC,GC过程会暂停所有线程(Stop-The-World),主线程也被挂起,累积后可能触发ANR。
ANR的定位方法
分析traces文件
ANR发生时,系统会将所有线程的堆栈快照写入traces文件:
bash# 旧版系统 adb pull /data/anr/traces.txt # Android 10+,traces文件按时间命名 adb pull /data/anr/anr_2026-05-28-14-30-00-000
分析步骤:
- 定位到自己的进程PID(搜索包名)
- 查看"main"线程状态,关注 Sleeping、Waiting、Monitor 等阻塞状态
- 沿堆栈从上往下找,定位到业务代码位置
- 如果主线程状态是 Monitor,说明在等锁,搜索持有该锁的线程
text"main" prio=5 tid=1 Monitor | group="main" sCount=1 dsCount=0 obj=0x73c12000 self=0xb8e2e800 at com.example.app.MyActivity.loadData(MyActivity.java:42) - waiting to lock <0x0d123456> (a java.lang.Object) held by thread "Worker-1"
使用Logcat过滤ANR信息
bashadb logcat | grep -E "am_anr|ANR in"
日志中会输出ANR的进程、原因、CPU使用情况。CPU使用率接近100%说明是计算密集型阻塞,CPU使用率很低说明是等锁或IO等待。
使用StrictMode检测主线程违规操作
kotlinif (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build() ) }
StrictMode只在Debug模式下启用,可以在开发阶段提前发现主线程的磁盘读写和网络请求。
使用性能分析工具
- Systrace:系统级性能追踪,能展示主线程每一帧的耗时分布,定位ANR前的卡顿点
- Android Studio CPU Profiler:方法级别的耗时分析,找出主线程最耗时的调用链
- Perfetto:Systrace的升级版,支持更长时间的性能追踪
ANR的解决方案
将耗时操作移到子线程
kotlin// Kotlin协程(推荐) lifecycleScope.launch(Dispatchers.IO) { val data = database.query() withContext(Dispatchers.Main) { updateUI(data) } } // 线程池 val executor = Executors.newFixedThreadPool(4) executor.execute { val data = database.query() runOnUiThread { updateUI(data) } }
避免死锁
- 统一锁的获取顺序,避免循环等待
- 使用
tryLock(timeout)替代lock(),设置超时避免永久阻塞 - 缩小锁的粒度,减少持锁时间
- 优先使用无锁方案,如
ConcurrentHashMap、AtomicInteger
优化广播接收器
kotlin// onReceive中不要执行耗时操作 override fun onReceive(context: Context, intent: Intent) { // 耗时任务交给WorkManager val request = OneTimeWorkRequestBuilder<DataProcessWorker>() .setInputData(workDataOf("key" to intent.getStringExtra("key"))) .build() WorkManager.getInstance(context).enqueue(request) }
线上ANR监控
kotlin// 使用FileObserver监听traces文件写入 class ANRMonitor(private val anrDir: String = "/data/anr") { private val observer = object : FileObserver(anrDir, FileObserver.CREATE) { override fun onEvent(event: Int, path: String?) { // 检测到新traces文件,上报ANR信息 reportANR(path) } } fun start() { observer.startWatching() } } // 使用Watchdog定时检测主线程是否阻塞 class ANRWatchdog(private val timeoutMs: Long = 5000) : Thread("ANR-Watchdog") { private val handler = Handler(Looper.getMainLooper()) @Volatile private var tick = 0L override fun run() { while (true) { val expectedTick = SystemClock.uptimeMillis() handler.post { tick = expectedTick } sleep(timeoutMs) if (tick != expectedTick) { // 主线程阻塞超过5秒,收集堆栈上报 reportANR(Looper.getMainLooper().thread.stackTrace) } } } }
面试追问
问:ANR和Crash有什么区别? ANR是主线程超时触发的系统对话框,进程仍在运行;Crash是未捕获异常导致的进程终止。关键区别:ANR时进程存活,Crash时进程死亡。但Crash可能引发ANR——异常处理过程中若阻塞了主线程,会先ANR再Crash。
问:traces.txt找不到业务代码怎么办? 说明ANR不是业务代码直接阻塞导致的。常见情况:系统GC暂停(主线程状态为 NATIVE 或 SUSPENDED)、Binder对端进程阻塞(看 Binder 线程堆栈)、系统资源竞争(看是否有系统锁持有者)。此时需要结合 Systrace 分析系统级行为。
问:线上ANR率怎么治理? 分三步:一是接入监控,使用 Watchdog 或 FileObserver 实时采集 ANR 堆栈;二是归因分类,将 ANR 按原因分为 IO 阻塞、锁等待、GC 频繁、Binder 超时等类型;三是逐类治理,IO 异步化、减少锁竞争、优化内存减少 GC、拆分跨进程调用。治理是持续过程,需要建立 ANR 率的看板和告警。