【Android】 ANR异常分析

一、简介

1、ANR(Application Not Responding)定义

在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。

2、ANR的类型

(1)KeyDispatchTimeout(常见)
input事件在5S内没有处理完成发生了ANR。
logcat日志关键字:Input event dispatching timed out

(2)BroadcastTimeout
前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
logcat日志关键字:Timeout of broadcast BroadcastRecord

(3)ServiceTimeout
前台Service:onCreate,onStart,onBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreate,onStart,onBind等生命周期在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service

(4)ContentProviderTimeout
ContentProvider 在10S内没有处理完成发生ANR。
logcat日志关键字:timeout publishing content providers

3、ANR出现的原因

(1)主线程频繁进行耗时的IO操作:如数据库读写
(2)多线程操作的死锁,主线程被block;
(3)主线程被Binder 对端block;
(4)System Server中WatchDog出现ANR;
(5)service binder的连接达到上线无法和和System Server通信
(6)系统资源已耗尽(管道、CPU、IO)

4、如何分析ANR

(1)日志分析:ANR发生时都会在log中输出错误信息,从log中可以获得ANR的类型,CPU的使用情况,CPU使用率过高有可能是CPU饥饿导致了ANR。CPU使用率过低说明主线程被block了,如果IOwait高是因为主线程进行I/O操作造成的。

(2)traces文件分析:除了log输出外,你会发现各个应用进程和系统进程的函数堆栈信息都输出到了一个/data/anr/traces.txt的文件中,这个文件是分析ANR原因的关键文件.要获取到该文件可使用adb指令进行赋权后拉出查看调用stack。通过log、trace.text、代码结合分析ANR的成因(iowait?Memoryleak?Block?)

(3)traces文件无法分析的:不过还存在一些ANR问题,trace文件是分析不了的,例如我们的系统上,人脸识别活体攻击的时候,native算法耗尽cpu资源导致其他app无法抢占cpu时间片导致anr,假如ANR的app是你开发的,估计查到死也找不到问题所在,类似这类问题也写过简要的分析文章:

二、案例分析

当ANR发生的时候,会将ANR记录到event log和main log中。

2.1 查看events_log(如果没有找到该文件可以跳过这一步)

查看mobilelog文件夹下的events_log,从日志中搜索关键字:am_anr,找到出现ANR的时间点、进程PID、ANR类型。

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

如日志:

07-20 15:36:36.472  1000  1520  1597 I am_anr  : [0,1480,com.xxxx.moblie,952680005,Input dispatching timed out (AppWindowToken{
    
    da8f666 token=Token{
    
    5501f51 ActivityRecord{
    
    15c5c78 u0 com.xxxx.moblie/.ui.MainActivity t3862}}}, Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)]

从上面的log我们可以看出: 应用com.xxxx.moblie 在07-20 15:36:36.472时间,发生了一次KeyDispatchTimeout类型的ANR,它的进程号是1480.
把关键的信息整理一下:

  • ANR时间:07-20 15:36:36.472
  • 进程pid:1480
  • 进程名:com.xxxx.moblie
  • ANR类型:KeyDispatchTimeout

我们已经知道了发生KeyDispatchTimeout的ANR是因为 input事件在5秒内没有处理完成。那么在这个时间07-20 15:36:36.472 的前5秒,也就是(15:36:30 ~15:36:31)时间段左右程序到底做了什么事情?这个简单,因为我们已经知道pid了,再搜索一下pid = 1480的日志.这些日志表示该进程所运行的轨迹,关键的日志如下:

07-20 15:36:29.749 10102  1480  1737 D moblie-Application: [Thread:17329] receive an intent from server, action=com.ttt.push.RECEIVE_MESSAGE
07-20 15:36:30.136 10102  1480  1737 D moblie-Application: receiving an empty message, drop
07-20 15:36:35.791 10102  1480  1766 I Adreno  : QUALCOMM build                   : 9c9b012, I92eb381bc9
07-20 15:36:35.791 10102  1480  1766 I Adreno  : Build Date                       : 12/31/17
07-20 15:36:35.791 10102  1480  1766 I Adreno  : OpenGL ES Shader Compiler Version: EV031.22.00.01
07-20 15:36:35.791 10102  1480  1766 I Adreno  : Local Branch                     : 
07-20 15:36:35.791 10102  1480  1766 I Adreno  : Remote Branch                    : refs/tags/AU_LINUX_ANDROID_LA.UM.6.4.R1.08.00.00.309.049
07-20 15:36:35.791 10102  1480  1766 I Adreno  : Remote Branch                    : NONE
07-20 15:36:35.791 10102  1480  1766 I Adreno  : Reconstruct Branch               : NOTHING
07-20 15:36:35.826 10102  1480  1766 I vndksupport: sphal namespace is not configured for this process. Loading /vendor/lib64/hw/gralloc.msm8998.so from the current namespace instead.
07-20 15:36:36.682 10102  1480  1480 W ViewRootImpl[MainActivity]: Cancelling event due to no window focus: KeyEvent {
    
     action=ACTION_UP, keyCode=KEYCODE_PERIOD, scanCode=0, metaState=0, flags=0x28, repeatCount=0, eventTime=16099429, downTime=16099429, deviceId=-1, source=0x101 }

从上面我们可以知道,在时间 07-20 15:36:29.749 程序收到了一个action消息。

07-20 15:36:29.749 10102  1480  1737 D moblie-Application: [Thread:17329] receive an intent from server, action=com.ttt.push.RECEIVE_MESSAGE。

原来是应用com.xxxx.moblie 收到了一个推送消息(com.ttt.push.RECEIVE_MESSAGE)导致了阻塞,我们再串联一下目前所获取到的信息:在时间07-20 15:36:29.749 应用com.xxxx.moblie 收到了一下推送信息action=com.ttt.push.RECEIVE_MESSAGE发生阻塞,5秒后发生了KeyDispatchTimeout的ANR。

2.2 查看main_log日志信息

在分析ANR的时候,我们首先要确认是不是当时CPU很紧张、各路APP都在抢占资源,CPU无法及时响应最终导致了ANR?为了排查这种情况,我们就需要获取ANR发生时候的CPU状态参数。

1、接下来我们来看一个ANR模拟日志案例

07-20 15:36:58.711  1000  1520  1597 E ActivityManager: ANR in com.xxxx.moblie (com.xxxx.moblie/.ui.MainActivity) (关键字ANR in + 进程名 + Activity名称)
07-20 15:36:58.711  1000  1520  1597 E ActivityManager: PID: 1480 (进程pid)
07-20 15:36:58.711  1000  1520  1597 E ActivityManager: Reason: Input dispatching timed out (AppWindowToken{
    
    da8f666 token=Token{
    
    5501f51 ActivityRecord{
    
    15c5c78 u0 com.xxxx.moblie/.ui.MainActivity t3862}}}, Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)(ANR的原因,输入分发超时)
07-20 15:36:58.711  1000  1520  1597 E ActivityManager: Load: 0.0 / 0.0 / 0.0 (Load表明是1分钟,5分钟,15分钟CPU的负载)
07-20 15:36:58.711  1000  1520  1597 E ActivityManager: CPU usage from 20ms to 20286ms later (2018-07-20 15:36:36.170 to 2018-07-20 15:36:56.436):
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   42% 6774/pressure: 41% user + 1.4% kernel / faults: 168 minor
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   34% 142/kswapd0: 0% user + 34% kernel
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   31% 1520/system_server: 13% user + 18% kernel / faults: 58724 minor 1585 major
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   13% 29901/com.ss.android.article.news: 7.7% user + 6% kernel / faults: 56007 minor 2446 major
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   13% 32638/com.android.quicksearchbox: 9.4% user + 3.8% kernel / faults: 48999 minor 1540 major
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   11% (CPU的使用率)1480/com.xxxx.moblie: 5.2%(用户态的使用率) user + (内核态的使用率) 6.3% kernel / faults: 76401 minor 2422 major
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   8.2% 21000/kworker/u16:12: 0% user + 8.2% kernel
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   0.8% 724/mtd: 0% user + 0.8% kernel / faults: 1561 minor 9 major
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   8% 29704/kworker/u16:8: 0% user + 8% kernel
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   7.9% 24391/kworker/u16:18: 0% user + 7.9% kernel
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   7.1% 30656/kworker/u16:14: 0% user + 7.1% kernel
07-20 15:36:58.711  1000  1520  1597 E ActivityManager:   7.1% 9998/kworker/u16:4: 0% user + 7.1% kernel

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

ANR发生的位置是:com.xxxx.moblie/.ui.MainActivity
com.xxxx.moblie 占用了11%的CPU,CPU的使用率并不是很高,基本可以排除CPU负载的原因
Reason提示我们是输入分发超时导致的ANR

2.3 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)

三、模拟触发ANR事件,获取trace文件

以下的几个案例,都是我通过代码故意触发ANR所产生的trace文件。

3.1 输入的事件在5秒内没有被响应,主线程被阻塞。

输入事件5s没有响应,如onClick事件。这是anr问题的主要类型,一般开发者不会犯这样的错,凡是耗时的操作都另开线程处理,如果疏忽了看看自己的代码就知道怎么处理。

(1)模拟触发ANR:

 findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
    
    
     @Override
     public void onClick(View v) {
    
    
         SystemClock.sleep(40000);//故意响应超时导致ANR
     }
 });

(2)对应的Trace文件关键日志:

DALVIK THREADS (14):  //虚拟机线程被阻塞
"Signal Catcher" daemon prio=5 tid=3 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x15980100 self=0x7322e16400
  | 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)

"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)

3.2 广播接收者的onReceive方法在10秒内没有执行完毕。

BroadcastReceiver是在程序主线程运行,而且默认情况下BroadcastReceiver的运行时间为10s,所以不能有耗时操作,如果耗时超过10s就会导致anr,从traces文件log就可以看出onReceive不能进行耗时任务。

(1)模拟触发ANR:

   IntentFilter intentFilter = new IntentFilter();
   intentFilter.addAction("com.anr.test");
   registerReceiver(new BroadcastReceiver() {
    
    
       @Override
       public void onReceive(Context context, Intent intent) {
    
    
           SystemClock.sleep(40000);//故意响应超时导致ANR
       }
   }, intentFilter);

(2)对应的Trace文件关键日志:

DALVIK THREADS (14):  //虚拟机线程被阻塞
"Signal Catcher" daemon prio=5 tid=3 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x13100088 self=0x7322e16400
  | sysTid=6639 nice=0 cgrp=default sched=0/0 handle=0x731c3304f0
  | state=R schedstat=( 6547603 5577708 11 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x731c235000-0x731c237000 stackSize=1009KB
  | held mutexes= "mutator lock"(shared held)

"main" prio=5 tid=1 Sleeping   //主线程被阻塞
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e64b08 self=0x7322e14c00
  | sysTid=6633 nice=-10 cgrp=default sched=0/0 handle=0x73a88cc548
  | state=S schedstat=( 605006551 138044643 533 ) utm=56 stm=3 core=2 HZ=100
  | stack=0x7fd05f2000-0x7fd05f4000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep(Native method)
  - sleeping on <0x08c05759> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:373)
  - locked <0x08c05759> (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$3.onReceive(MainActivity.java:80)  //广播接收者的onReceive方法
  at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1391)
  at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(lambda:-1)
  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)

BroadcastReceiver是Android的消息组件,用来组件之间、应用之间的消息传递,生命周期太短也不能开子线程处理耗时任务,耗时任务一般转交给IntentService或者JobIntentService去做。

3.3 服务Service没有及时响应

Service是计算型组件,虽然在后台运行,但是本质上它也跑在主线程,如果你的服务要做任何CPU密集型(如MP3播放)或阻塞(如网络)操作,都要放在子线程中,否则耗时超过20s就会导致anr。下面我在onStartCommand方法中休眠40秒。

(1)模拟触发ANR:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        SystemClock.sleep(40000);//故意响应超时导致ANR
        return super.onStartCommand(intent, flags, startId);
    }

(2)对应的Trace文件关键日志:

DALVIK THREADS (13):  //虚拟机线程被阻塞
"Signal Catcher" daemon prio=5 tid=3 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x13940108 self=0x7322e16400
  | sysTid=7658 nice=0 cgrp=default sched=0/0 handle=0x731c3304f0
  | state=R schedstat=( 5995782 10695989 10 ) utm=0 stm=0 core=3 HZ=100
  | stack=0x731c235000-0x731c237000 stackSize=1009KB
  | held mutexes= "mutator lock"(shared held)

"main" prio=5 tid=1 Sleeping   //主线程被阻塞
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e64b08 self=0x7322e14c00
  | sysTid=7652 nice=-10 cgrp=default sched=0/0 handle=0x73a88cc548
  | state=S schedstat=( 595004855 101962599 596 ) utm=40 stm=18 core=2 HZ=100
  | stack=0x7fd05f2000-0x7fd05f4000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep(Native method)
  - sleeping on <0x0e096f2a> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:373)
  - locked <0x0e096f2a> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:314)
  at android.os.SystemClock.sleep(SystemClock.java:127)  //代码被阻塞的关键位置
  at com.example.anrtest.ANRService.onStartCommand(ANRService.java:17)  //服务的onStartCommand方法
  at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3705)
  at android.app.ActivityThread.access$1600(ActivityThread.java:200)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1688)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  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)

四、参考

猜你喜欢

转载自blog.csdn.net/daokedream/article/details/129977302