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 隔离。