如何分析ANR日志(记录一次我遇到的ANR)

前言

公司的App一直是混淆后再上线,一直没问题,前段时间公司的Android开发组组长拉了一个Android 13分支,升级了Android项目版本从api 28(Android 9)升级Android api 33(Android 13),加了百度地图的混淆的代码之后,打混淆后的release包后(不混淆的release包没问题),在一些手机上(Redmi Note 7 Android 10系统、小米8 Android 10系统)每次首次运行App就会ANR,后来这个问题交由我来看一下。

1.定位问题

(1)首先把ANR日志导出来

通过adb bugreport 指令生成zip,在终端(Mac电脑上是终端;Windows电脑上是dos命令窗口,快捷键win+R)输入adb bugreport /Users/erwinnakashima/AndroidStudioProjects_pri,/Users/erwinnakashima/AndroidStudioProjects_pri即为文件保存的路径,然后回车,如下图一

(2)trace文件格式解析

解压刚刚下载的bugreport-sdk_gphone64_arm64-SE1A.211012.001-2023-05-29-21-47-21.zip文件,打开文件可以看到类似于如下的文件内容:

"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x75fb93c8 self=0x7e0bda4000
  | sysTid=9362 nice=-10 cgrp=default sched=0/0 handle=0x7e0d30bed0
  | state=S schedstat=( 1783522147 140816011 2145 ) utm=135 stm=43 core=7 HZ=100
  | stack=0x7fd150e000-0x7fd1510000 stackSize=8192KB
  | held mutexes=
  at java.lang.Object.wait(Native method)
  - waiting on <0x0ff7a37c> (a com.baidu.pano.platform.http.tool.j)
  at java.lang.Object.wait(Object.java:442)
  at com.baidu.pano.platform.http.tool.j.a(RequestFuture.java:18)
  - locked <0x0ff7a37c> (a com.baidu.pano.platform.http.tool.j)
  at com.baidu.pano.platform.http.tool.j.get(RequestFuture.java:1)
  at com.baidu.pano.platform.util.d.A(HttpExecutor.java:19)
  at com.baidu.pano.platform.http.b.f(BannerRequest.java:156)
  at com.baidu.lbsapi.BMapManager$1.onAuthResult(BMapManager.java:40)
  at com.baidu.lbsapi.auth.k.handleMessage(unavailable:-1)
  at android.os.Handler.dispatchMessage(Handler.java:107)
  at android.os.Looper.loop(Looper.java:224)
  at android.app.ActivityThread.main(ActivityThread.java:7590)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

首先需要了解这些参数表示的意义,我们挑其中关键的几个说明:

第一行:

"main" prio=5 tid=1 Waiting

main prio=5 tid=1 NATIVE说明了线程名称、线程的优先级、线程锁id和线程状态。线程名称是启动线程的时候手动指明的,这里的main标识是主线程,是Android自动设定的一个线程名称,如果是自己手动创建的线程,一般会被命名成“Thread-xx”的格式,其中xx是线程id,它只增不减不会被复用;注意这其中的tid不是线程的id,它是一个在Java虚拟机中用来实现线程锁的变量,随着线程的增减,这个变量的值是可能被复用的;最后线程的状态还分为如下几种

扫描二维码关注公众号,回复: 15264549 查看本文章

特别说明一下MONITOR状态和SUSPEND状态,MONITOR状态一般是类的同步块或者同步方法造成的,SUSPENDED状态在debugger的时候会出现,可以用来区别是不是真的是用户正常操作跑出了ANR。

第二行:

group="main" sCount=1 dsCount=0 flags=1 obj=0x75fb93c8 self=0x7e0bda4000

group是线程组名称。sCount是此线程被挂起的次数,dsCount是线程被调试器挂起的次数,当一个进程被调试后,sCount会重置为0,调试完毕后sCount会根据是否被正常挂起增长,但是dsCount不会被重置为0,所以dsCount也可以用来判断这个线程是否被调试过。obj表示这个线程的Java对象的地址,self表示这个线程本身的地址。

第三行:此后是线程的调度信息

sysTid=9362 nice=-10 cgrp=default sched=0/0 handle=0x7e0d30bed0

sysTid是Linux下的内核线程id,nice是线程的调度优先级,sched分别标志了线程的调度策略和优先级,cgrp是调度属组,handle是线程的处理函数地址。

第四行:然后是线程当前上下文信息

state=S schedstat=( 1783522147 140816011 2145 ) utm=135 stm=43 core=7 HZ=100

state是调度状态;schedstat从 /proc/[pid]/task/[tid]/schedstat读出,三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度,有的android内核版本不支持这项信息,得到的三个值都是0;utm是线程用户态下使用的时间值(单位是jiffies);stm是内核态下的调度时间值;core是最后执行这个线程的cpu核的序号。

最后就是这个线程的调用栈信息。

(3)通过分析trace文件得到ANR信息:

通过上面分析,可以看到trace文件的头部就包含了很多与该线程相关的信息,但是并不是每个信息我们都必须弄懂,排查ANR的时候只需要找到其中关键的几个信息即可。一般可以通过以下几个简单的方法来判断。

trace文件顶部的线程一般是ANR的元凶

这是一个简单的方法,但是大部分情况下都适用,可以通过这个方法来快速判断是否是自己的应用造成了本次ANR。说明以下,并不是trace文件包含的应用就一定是造成ANR的帮凶,应用出现在trace文件中,只能说明出现ANR的时候这个应用进程还活着,trace文件的顶部则是触发ANR的应用信息。因此,如果你的应用出现在了trace文件的顶部,那么很有可能是因为你的应用造成了ANR,否则是你的应用造成ANR的可能性不大,但是具体是不是还需要进一步分析。例如:

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x75fb93c8 self=0x7e0bda4000
  | sysTid=1953 nice=-10 cgrp=default sched=0/0 handle=0x7e0d30bed0
  | state=S schedstat=( 122946149446 23547274620 367613 ) utm=9353 stm=2941 core=4 HZ=100
  | stack=0x7fd150e000-0x7fd1510000 stackSize=8192KB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/1953/stack)
  native: #00 pc 00000000000d1208  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
  native: #01 pc 0000000000018120  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  native: #02 pc 0000000000017ff0  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+56)
  native: #03 pc 000000000013ec9c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:336)
  at android.os.Looper.loop(Looper.java:181)
  at com.android.server.SystemServer.run(SystemServer.java:568)
  at com.android.server.SystemServer.main(SystemServer.java:367)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:928)

查看

at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:336)
  at android.os.Looper.loop(Looper.java:181)
  at com.android.server.SystemServer.run(SystemServer.java:568)
  at com.android.server.SystemServer.main(SystemServer.java:367)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:928)

我们可以发现,此次ANR发生于system_server获取用户事件的native方法里面,并不是我们的应用造成了ANR。

(4)经过以上分析,我们发现这个就是我们需要ANR的信息。

"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x75fb93c8 self=0x7e0bda4000
  | sysTid=9362 nice=-10 cgrp=default sched=0/0 handle=0x7e0d30bed0
  | state=S schedstat=( 1783522147 140816011 2145 ) utm=135 stm=43 core=7 HZ=100
  | stack=0x7fd150e000-0x7fd1510000 stackSize=8192KB
  | held mutexes=
  at java.lang.Object.wait(Native method)
  - waiting on <0x0ff7a37c> (a com.baidu.pano.platform.http.tool.j)
  at java.lang.Object.wait(Object.java:442)
  at com.baidu.pano.platform.http.tool.j.a(RequestFuture.java:18)
  - locked <0x0ff7a37c> (a com.baidu.pano.platform.http.tool.j)
  at com.baidu.pano.platform.http.tool.j.get(RequestFuture.java:1)
  at com.baidu.pano.platform.util.d.A(HttpExecutor.java:19)
  at com.baidu.pano.platform.http.b.f(BannerRequest.java:156)
  at com.baidu.lbsapi.BMapManager$1.onAuthResult(BMapManager.java:40)
  at com.baidu.lbsapi.auth.k.handleMessage(unavailable:-1)
  at android.os.Handler.dispatchMessage(Handler.java:107)
  at android.os.Looper.loop(Looper.java:224)
  at android.app.ActivityThread.main(ActivityThread.java:7590)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

我们可以看到com.baidu.pano.platform.http.tool.j这句话,发现是百度全景sdk的问题。

然后在App首次打开的时候,用到了百度地图Andriod全景SDK,在初始化的时候,调用了以下方法。

//context为applicationContext
mBMapManager = new BMapManager(context);
//设置用户是否同意隐私政策,自v2.9.2版本起增加了隐私合规接口,请务必确保用户同意隐私政策后调用setAgreePrivacy接口
//true,表示用户同意隐私合规政策
//false,表示用户不同意隐私合规政策
mBMapManager.setAgreePrivacy(PanoDemoActivity.this.getApplicationContext(), true);
// 设置初始化监听  
mBMapManager.init(new MyGeneralListener()); 

分析了一下,就可以发现是调用mBMapManager.init函数发生ANR了,经过查看与分析,具体ANR是在这一行at com.baidu.lbsapi.BMapManager$1.onAuthResult(BMapManager.java:40)发生的。

2.解决问题:

(1)因为全景sdk用的还是有些旧的sdk,可能新版的已经解决了这个问题,所以先升级sdk,然而没有任何效果。


(2)根据报错行数,在BMapManager.onAuthResult函数内,发现使用了apache网络框架进行了请求,而且查看Logcat日志发现apache进行请求的时候报错如下:

2023-02-20 09:11:51.499 29352-29780/? E/AndroidRuntime: FATAL EXCEPTION: Thread-30
    Process: com.ccbhome.lanhai, PID: 29352
    java.lang.IncompatibleClassChangeError: Class 'org.apache.http.message.BasicHeader' does not implement interface 'org.apache.http.NameValuePair' in call to 'java.lang.String org.apache.http.NameValuePair.getName()' (declaration of 'com.baidu.pano.platform.http.tool.a' appears in /data/app/com.ccbhome.lanhai-XbPnn0o0P6zMCczOA5K8fQ==/base.apk!classes16.dex)
        at com.baidu.pano.platform.http.tool.a.a(BasicNetwork.java:72)
        at com.baidu.pano.platform.http.tool.a.a(BasicNetwork.java:8)
        at com.baidu.pano.platform.http.i.run(NetworkDispatcher.java:40)

 我们确定应该是apache网络框架的原因,解决方式'org.apache.http.message.BasicHeader' does not implement interface 'org.apache.http.NameValuePair'_搬仓鼠的博客-CSDN博客

从博客中我们可以看到

红圈中的内容和我们当前的问题还差不多(我们用到的百度sdk内部报的错误,猜测可能不能直接反映到我们的App上,App不是崩溃,而是ANR),所以我们按照博客说的改进一下,然而又没有任何效果。

(3)目前我们只能联系百度sdk官方(百度sdk官方的确兼容性有问题,当然了不止这一个兼容性问题)解决一下这个问题了,我给百度sdk官方开发说,打混淆包之后,BMapManager.onAuthResult这个会造成ANR,并且给他说可能是因为apache网络框架造成的问题:

1)百度sdk官方先是让我把混淆代码改了一下,只是,没什么用,因为该加的我早就全都加了。

2)他让我试一下这个方法'org.apache.http.message.BasicHeader' does not implement interface 'org.apache.http.NameValuePair'_搬仓鼠的博客-CSDN博客

但是我已经试过了,没有任何效果。

3)百度sdk官方开发把他的demo项目和我的Android 13分支项目的sdk版本和全景sdk版本都改成一致的,然后用了华为手机试了一下,并没有发现问题,我让他用和我一样的手机试一下(Redmi Note 7 Android 10系统),只是他们那并没有这种手机,后来他找了一个小米手机(小米8 Android 10系统)试了一下,发现果然ANR了。

我给他的建议是把apache网络框架改成okhttp3网络框架试一下,只是他的sdk不能说改就改,后来,他这个问题说排期了,两个月以后解决,后来跟进了一下这个问题,百度sdk官方把网络框架从apache换成okhttp3了,经过调试,发现没有问题了。

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:RxJava2和Retrofit2项目,添加自动管理token功能,添加RxJava2生命周期管理,使用App架构设计是MVP模式和MVVM模式,同时使用组件化,部分代码使用Kotlin,此项目持续维护中。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

猜你喜欢

转载自blog.csdn.net/NakajimaFN/article/details/130936032
ANR
今日推荐