深入浅出分析traces日志
一、traces日志是什么?
traces日志是安卓系统的"法医报告",记录了:
- 所有线程的状态(包括主线程)
- 锁的持有和等待关系
- 方法调用堆栈
- ANR发生时的现场快照
二、如何获取traces日志?
1. 通过adb命令
# 获取当前traces文件
adb pull /data/anr/traces.txt
# 获取历史ANR记录(需要root)
adb pull /data/anr/anr_*
2. 通过代码触发
// 手动生成当前线程堆栈
StringBuilder sb = new StringBuilder();
for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
sb.append(ste.toString()).append("\n");
}
Log.e("TRACE", sb.toString());
三、traces日志结构解析
一份典型的traces文件包含多个线程信息,结构如下:
----- pid 12345 at 2023-01-01 12:00:00 -----
Cmd line: com.example.app // 进程名
Build fingerprint: ... // 设备信息
"main" prio=5 tid=1 Native // 线程名/优先级/线程ID/状态
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12c12345
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7f8a12345678
| state=S schedstat=( 123456789 987654321 1234 ) utm=12 stm=34 core=0 HZ=100
| stack=0x7fc1234000-0x7fc1236000 stackSize=8MB
| held mutexes=
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke!(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
关键字段解读
| 字段 | 说明 |
|---|---|
| prio | 线程优先级(5是默认值) |
| tid | 线程ID(1通常是主线程) |
| 状态 | Native/Sleeping/Blocked等 |
| held mutexes | 当前持有的锁 |
| stack | 调用堆栈(倒序,最上层在最前面) |
四、ANR问题分析实战
案例1:主线程阻塞
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12c12345
| held mutexes=
at com.example.app.MainActivity.onCreate(MainActivity.java:25)
- waiting to lock <0x0456789> (a java.lang.Object)
held by thread 3 (AsyncTask #1)
分析步骤:
- 主线程在
MainActivity.java:25等待锁0x0456789 - 查找线程3:
"AsyncTask #1" prio=5 tid=3 TimedWaiting | held mutexes= <0x0456789> (a java.lang.Object) - 结论:AsyncTask持有了锁但未释放,导致主线程阻塞
案例2:Binder调用超时
"main" prio=5 tid=1 Native
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7f8a12345678
| state=S schedstat=( 123456789 987654321 1234 ) utm=12 stm=34 core=0 HZ=100
| stack=0x7fc1234000-0x7fc1236000 stackSize=8MB
| held mutexes=
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:615)
at android.app.IActivityManager$Stub$Proxy.contentProviderExists(IActivityManager.java:12345)
at com.example.app.provider.MyProvider.call(MyProvider.java:67)
分析:
- 主线程在等待AMS的Binder调用返回
- 解决方案:避免主线程同步调用系统服务
五、高级分析技巧
1. 锁竞争分析
查找所有包含waiting to lock和held mutexes的线程:
grep -E "waiting to lock|held mutexes" traces.txt
2. 死锁检测
当出现循环等待时:
线程A:
waiting to lock <0x123> (held by 线程B)
线程B:
waiting to lock <0x456> (held by 线程A)
3. 结合systrace
# 生成带Java堆栈的systrace
python systrace.py --app=com.example.app dalvik -o trace.html
六、常见问题模式
1. 主线程IO
at java.io.FileInputStream.open(FileInputStream.java)
at com.example.app.Utils.readFile(Utils.java:123)
2. 过度布局
at android.view.View.layout(View.java:12345)
at com.example.app.CustomView.onLayout(CustomView.java:67)
3. 第三方库问题
at com.some.library.NetworkHelper.syncRequest(NetworkHelper.java:56)
七、分析工具推荐
- Android Studio Profiler:可视化分析
- Thread Dump Analyzer:自动化分析工具
- python脚本:自定义日志分析
# 简单分析脚本示例
with open('traces.txt') as f:
for line in f:
if 'waiting to lock' in line:
print(line.strip())
总结
- 先定位主线程:查找
main线程状态和堆栈 - 关注锁信息:
waiting to lock和held mutexes - 结合代码上下文:对照行号分析业务逻辑
- 多维度验证:结合systrace/logcat
掌握traces分析,你就能像侦探一样破解ANR谜案! 🕵️♂️