Android中ANR的分析和解决( 四 )

通过上面所提供的案例我们可以分析出以下几点:

  • ANR发生的位置是:com.example.anrtest/.MainActivity
  • CPU的使用率都不是很高 , 基本可以排除CPU负载的原因
  • Reason提示我们是输入分发超时导致的ANR
通过上面几点我们虽然排除了CPU过度负载的可能 , 但我们并不能准确定位出ANR的确切位置 , 要想准确定位出ANR发生的确切位置 , 就要借助系统为了解决ANR问题而提供的终极大杀器——traces.txt文件了 。
五、 traces.txt 文件分析当APP不响应、响应慢了、或者WatchDog的监视没有得到回应时 , 系统就会dump出一个traces.txt文件 , 存放在文件目录:/data/anr/文件夹中 , 通过traces文件 , 我们可以拿到线程名、堆栈信息、线程当前状态、binder call等信息 。
我们可以通过adb命令获取到该文件夹下面的所有traces文件:adb pull /data/anr
trace:Cmd line:com.xxxx.moblie"main" prio=5 tid=1 Runnable| group="main" sCount=0 dsCount=0 obj=0x73bcc7d0 self=0x7f20814c00| sysTid=20176 nice=-10 cgrp=default sched=0/0 handle=0x7f251349b0| state=R schedstat=( 0 0 0 ) utm=12 stm=3 core=5 HZ=100| stack=0x7fdb75e000-0x7fdb760000 stackSize=8MB| held mutexes= "mutator lock"(shared held)// java 堆栈调用信息,可以查看调用的关系 , 定位到具体位置at ttt.push.InterceptorProxy.addMiuiApplication(InterceptorProxy.java:77)at ttt.push.InterceptorProxy.create(InterceptorProxy.java:59)at android.app.Activity.onCreate(Activity.java:1041)at miui.app.Activity.onCreate(SourceFile:47)at com.xxxx.moblie.ui.b.onCreate(SourceFile:172)at com.xxxx.moblie.ui.MainActivity.onCreate(SourceFile:68)at android.app.Activity.performCreate(Activity.java:7050)at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2807)at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2929)at android.app.ActivityThread.-wrap11(ActivityThread.java:-1)at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1618)at android.os.Handler.dispatchMessage(Handler.java:105)at android.os.Looper.loop(Looper.java:171)at android.app.ActivityThread.main(ActivityThread.java:6699)at java.lang.reflect.Method.invoke(Native method)at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)这里详细解析一下traces.txt里面的一些字段 , 看看它到底能给我们提供什么信息.
  • main:main标识是主线程 , 如果是线程 , 那么命名成“Thread-X”的格式,x表示线程id,逐步递增 。
  • prio:线程优先级,默认是5
  • tid:tid不是线程的id , 是线程唯一标识ID
  • group:是线程组名称
  • sCount:该线程被挂起的次数
  • dsCount:是线程被调试器挂起的次数
  • obj:对象地址
  • self:该线程Native的地址
  • sysTid:是线程号(主线程的线程号和进程号相同)
  • nice:是线程的调度优先级
  • sched:分别标志了线程的调度策略和优先级
  • cgrp:调度归属组
  • handle:线程处理函数的地址 。
  • state:是调度状态
  • schedstat:从 /proc/[pid]/task/[tid]/schedstat读出 , 三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度 , 不支持这项信息的三个值都是0;
  • utm:是线程用户态下使用的时间值(单位是jiffies)
  • stm:是内核态下的调度时间值
  • core:是最后执行这个线程的cpu核的序号 。
Java的堆栈信息是我们最关心的 , 它能够定位到具体位置 。从上面的traces,我们可以判断ttt.push.InterceptorProxy.addMiuiApplicationInterceptorProxy.java:77 导致了com.xxxx.moblie发生了ANR 。这时候可以对着源码查看 , 找到出问题 , 并且解决它 。
接下来再看一个真实的导致ANR的trace案例:
//开头显示进程号、ANR发生的时间点和进程名称----- pid 4972 at 2022-10-09 16:45:41 -----Cmd line: com.example.anrtest...省略...DALVIK THREADS (14)://以下是各个线程的函数堆栈信息//依次是:线程名(后面标识有daemon说明是个守护线程)、线程优先级、线程号、线程当前状态(TIMED_WAIT或SUSPENDED在anr时很常见)"Signal Catcher" daemon prio=5 tid=3 Runnable//依次是:线程组名称、suspendCount个数、debugSuspendCount个数、标记 , 线程的Java对象地址、线程的Native对象地址| group="system" sCount=0 dsCount=0 flags=0 obj=0x15980100 self=0x7322e16400//sysTid是线程号 , 主线程的线程号和进程号相同| sysTid=4978 nice=0 cgrp=default sched=0/0 handle=0x731c3304f0| state=R schedstat=( 5583230 6778645 10 ) utm=0 stm=0 core=3 HZ=100| stack=0x731c235000-0x731c237000 stackSize=1009KB| held mutexes= "mutator lock"(shared held)//“Signal Catcher”负责接收和处理kernel发送的各种信号 , 例如SIGNAL_QUIT、SIGNAL_USR1等就是被该线程//接收到 , 这个文件的内容就是由该线程负责输出的 , 可以看到它的状态是RUNNABLE , 不过此线程也不需要关心...省略...//主线程 , 当前处于休眠状态"main" prio=5 tid=1 Sleeping| group="main" sCount=1 dsCount=0 flags=1 obj=0x73e64b08 self=0x7322e14c00| sysTid=4972 nice=-10 cgrp=default sched=0/0 handle=0x73a88cc548| state=S schedstat=( 618097798 184039398 700 ) utm=56 stm=5 core=3 HZ=100| stack=0x7fd05f2000-0x7fd05f4000 stackSize=8MB| held mutexes=at java.lang.Thread.sleep(Native method)- sleeping on <0x0d9c45cc> (a java.lang.Object)at java.lang.Thread.sleep(Thread.java:373)- locked <0x0d9c45cc> (a java.lang.Object)at java.lang.Thread.sleep(Thread.java:314)at android.os.SystemClock.sleep(SystemClock.java:127)at com.example.anrtest.MainActivity$1.onClick(MainActivity.java:57)at android.view.View.performClick(View.java:6597)at android.view.View.performClickInternal(View.java:6574)at android.view.View.access$3100(View.java:778)at android.view.View$PerformClick.run(View.java:25885)at android.os.Handler.handleCallback(Handler.java:873)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loop(Looper.java:193)at android.app.ActivityThread.main(ActivityThread.java:6718)at java.lang.reflect.Method.invoke(Native method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)...省略..."Jit thread pool worker thread 0" daemon prio=5 tid=2 Native| group="main" sCount=1 dsCount=0 flags=1 obj=0x15980000 self=0x731c029000| sysTid=4977 nice=9 cgrp=default sched=0/0 handle=0x731c4314f0| state=S schedstat=( 122669387 79479997 188 ) utm=8 stm=3 core=3 HZ=100| stack=0x731c333000-0x731c335000 stackSize=1021KB| held mutexes=...省略...//Binder线程是进程的线程池中用来处理binder请求的线程"Binder:4972_1" prio=5 tid=9 Native| group="main" sCount=1 dsCount=0 flags=1 obj=0x159829a0 self=0x731a60d000| sysTid=4984 nice=0 cgrp=default sched=0/0 handle=0x73099b34f0| state=S schedstat=( 12586983 41079166 22 ) utm=1 stm=0 core=3 HZ=100| stack=0x73098b8000-0x73098ba000 stackSize=1009KB| held mutexes=...省略...


推荐阅读