5月28日 01:53

Android中ANR是什么,如何定位和解决ANR问题?

ANR是什么?

ANR(Application Not Responding)是Android系统的一种保护机制。当应用主线程在规定时间内无法响应用户操作或系统事件时,系统会弹出"应用无响应"对话框,让用户选择继续等待或强制关闭。

ANR不是崩溃(Crash),二者本质不同:Crash是程序异常导致的进程终止,ANR是主线程阻塞导致的超时告警。一个Crash的进程也可能同时触发ANR——如果主线程在异常处理过程中阻塞了输入事件分发。

ANR的触发条件

类型超时阈值触发场景
输入事件ANR5秒按键/触摸事件未在窗口内完成分发
前台广播ANR10秒onReceive()执行超时
后台广播ANR60秒后台广播接收器超时
前台Service ANR20秒Service生命周期方法执行超时
后台Service ANR200秒后台Service超时
ContentProvider ANR10秒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 }

死锁

主线程等待子线程持有的锁,子线程又等待主线程持有的锁,形成循环等待。

kotlin
private 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

分析步骤:

  1. 定位到自己的进程PID(搜索包名)
  2. 查看"main"线程状态,关注 Sleeping、Waiting、Monitor 等阻塞状态
  3. 沿堆栈从上往下找,定位到业务代码位置
  4. 如果主线程状态是 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信息

bash
adb logcat | grep -E "am_anr|ANR in"

日志中会输出ANR的进程、原因、CPU使用情况。CPU使用率接近100%说明是计算密集型阻塞,CPU使用率很低说明是等锁或IO等待。

使用StrictMode检测主线程违规操作

kotlin
if (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(),设置超时避免永久阻塞
  • 缩小锁的粒度,减少持锁时间
  • 优先使用无锁方案,如 ConcurrentHashMapAtomicInteger

优化广播接收器

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 率的看板和告警。

标签:Android