深入浅出分析traces日志

359 阅读2分钟

深入浅出分析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)

分析步骤

  1. 主线程在MainActivity.java:25等待锁0x0456789
  2. 查找线程3:
    "AsyncTask #1" prio=5 tid=3 TimedWaiting
      | held mutexes= <0x0456789> (a java.lang.Object)
    
  3. 结论: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 lockheld 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)

七、分析工具推荐

  1. Android Studio Profiler:可视化分析
  2. Thread Dump Analyzer:自动化分析工具
  3. python脚本:自定义日志分析
# 简单分析脚本示例
with open('traces.txt') as f:
    for line in f:
        if 'waiting to lock' in line:
            print(line.strip())

总结

  1. 先定位主线程:查找main线程状态和堆栈
  2. 关注锁信息waiting to lockheld mutexes
  3. 结合代码上下文:对照行号分析业务逻辑
  4. 多维度验证:结合systrace/logcat

掌握traces分析,你就能像侦探一样破解ANR谜案! 🕵️♂️