ANR日志与Trace文件:logcat抓取方法、traces.txt定位、main thread堆栈解读

现象识别:ANR发生时你能看到什么

当Compose应用的UI线程(Main Thread)被阻塞超过5秒(输入事件超时)或广播未在10秒内完成时,系统会弹出“应用无响应”(Application Not Responding)对话框。此时用户可以选择“等待”或“关闭应用”。从开发者视角,我们需要在logcat和本地trace文件中捕获关键证据。

现象用户感知系统行为
应用界面冻结触摸、滑动无反应,画面卡顿超过5秒InputDispatcher 触发 ANR
弹窗提示“XXX 应用无响应,是否关闭?”系统进程 (system_server) 弹出对话框
后台日志无直接感知logcat 输出 ANR 事件,同时写入 /data/anr/traces.txt
排查关键点:一旦确认应用出现“无响应”弹窗,应立即采集日志,避免因后续用户操作覆盖历史trace。

数据采集:两种核心日志的抓取方法

方法一:实时logcat抓取ANR事件

在ANR发生前后,通过adb过滤系统级ANR消息:

# 实时观察所有ANR相关输出
adb logcat -v time | grep -E "(ANR|am_anr|InputDispatcher)"

# 如果已经知道包名,可以加上包名过滤
adb logcat -v time | grep -E "com.example.yourapp" | grep -E "(ANR|am_anr)"

典型ANR日志行示例:

11-15 10:23:45.678  1234  5678 I am_anr  : [0,12345,com.example.yourapp,12345,InputEvent,,-1,false]
11-15 10:23:45.789  1234  5678 I WindowManager: ANR in com.example.yourapp
11-15 10:23:45.790  1234  5678 I WindowManager: Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it.)

方法二:导出traces.txt文件

ANR发生后,系统会 dump 所有线程的堆栈到 /data/anr/traces.txt(需要root或debug权限)。推荐使用 adb bugreport 一次性获取完整诊断包:

# 方式1:直接pull(需要root)
adb root
adb pull /data/anr/traces.txt ./traces_anr.txt

# 方式2:使用bugreport(无需root,但需要开启开发者选项)
adb bugreport ./bugreport.zip
# 解压后在 FS/data/anr/ 下找到 traces.txt
注意事项:traces.txt 是**增量写入**的,每次ANR会追加,但文件大小有限。线上环境可能因权限无法直接读取,建议使用 adb bugreport 或集成Tombstone监听库。

原因定位:main thread堆栈解读

traces.txt 中每个线程以 "main" 开头标识主线程。我们需要重点关注主线程的堆栈帧,判断当前被什么操作阻塞。

堆栈结构速查表

堆栈特征典型含义判断规则
java.lang.Thread.sleep 或 Object.wait主线程主动等待(非预期)超时时间 > 5s → 肯定ANR
MessageQueue.nativePollOnce 后紧跟 Handler.dispatchMessage主线程在正常处理消息,但某条消息耗时过长看 dispatchMessage 之后的帧,定位具体业务代码
InputMethodManager、ViewRootImpl 相关锁等待主线程被输入法或布局锁阻塞常伴有 waiting to lock 或 blocked on
android.app.ActivityThread.H.handleMessage主线程正在处理Activity生命周期或广播查看内部是否有耗时IO或网络请求
kotlinx.coroutines 相关帧协程在错误调度器上运行检查是否在 Dispatchers.Main 下执行了阻塞操作

实际解读案例

从Compose应用抓取的一段traces.txt主线程堆栈(简化):

"main" prio=5 tid=1 Blocked
  at com.bumptech.glide.Glide.load(Glide.java:876)
  - waiting to lock <0x0b4c> (a com.bumptech.glide.Registry)
  at com.example.app.ui.ImageLoader.loadImage(ImageLoader.kt:45)
  at com.example.app.ui.components.ImageComposableKt.ImageComposable(ImageComposable.kt:30)
  at androidx.compose.runtime.ComposerImpl$doComposition$2.invoke(ComposerImpl.kt:435)
  at androidx.compose.runtime.CompositionImpl$setContent$3$1$1.invoke(CompositionImpl.kt:160)
  at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.kt:1423)
  ... (more View draw frames)

解读:主线程第3帧在Glide加载图片时等待锁,同时Compose重组流程被阻塞。Glide加载图片往往涉及磁盘IO或网络,应迁移到子线程。

修复动作(简要)

基于main thread堆栈定位后,典型修复动作包括:

  • 使用withContext(Dispatchers.IO) 将文件/网络操作移出主线程。
  • 图片加载库设置异步加载策略(如Glide默认异步,但需检查缓存逻辑)。
  • 避免在主线程使用 Thread.sleep、Looper.getMainLooper().getQueue().addIdleHandler() 中执行耗时任务。

复测验证

修复后,需要模拟可复现的ANR场景重新测试:

# 使用Monkey测试随机事件
adb shell monkey -p com.example.yourapp --throttle 300 5000

# 或使用自动点击脚本触发高负载操作,同时观察logcat
adb logcat -v time | grep -E "am_anr|ANR" > anr_check.log

如果30分钟内不再出现ANR日志,且traces.txt无新增主线程阻塞记录,则修复有效。

预防方案

  • 在开发阶段启用 StrictMode 检测主线程磁盘/网络访问:
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .penaltyDeath()  // 开发阶段可设为闪退,强制修正
            .build()
    )
}
  • 持续集成阶段加入ANR trace自动化检查(验证traces.txt中主线程无异常锁等待)。
  • 使用 Google Play ANR 报告 监控线上ANR率。
Compose特别提醒:重组(Recomposition)期间如果主线程执行了 calculateInBackground 等同步阻塞操作,也会导致ANR。确保所有副作用(SideEffect)中的耗时逻辑都使用 LaunchedEffect + Dispatchers.IO 隔离。
ANR日志采集与main thread堆栈定位流程 用户无响应弹窗 系统生成traces.txt /data/anr/traces.txt logcat实时输出 adb logcat | grep ANR Pull traces.txt adb pull /data/anr/ 获取logcat文件 保存ANR时间戳块 定位main线程堆栈 找到 tid=1 "main" 块 判断阻塞根因 等待锁?IO操作?布局重绘?协程误用? 提示:拿到traces.txt后先确认是否存在“main”线程——若无此块,可能ANR并非由主线程阻塞引起(例如Service超时)。