XXXXX
C/C++
XXXXX
C#/.net
XXXXX
js
java
java
开发导航 开发导航 www.endv.cn
天云

导致ANR的几种情况

导致ANR的几种情况

  • KeyDispatchTimeout(5s): 按键或触摸事件在特定时间内无法处理完成
  • BroadcastTimeout(前台10s,后台60s): 广播在特定时间内无法处理完成
  • ServiceTimeout(前台20s,后台200s): Service在特定的时间无法处理完成 另外还有ProviderTimeout和WatchDog等导致的ANR

常见的原因

    A.耗时操作,如复杂的layout,庞大的for循环,IO等。
B.被Binder 对端block
C.被子线程同步锁block
    D.Binder被占满导致主线程无法和SystemServer通信
E.得不到系统资源(CPU/RAM/IO)
其中ABCD比较好分析,而E比较困难。

应用ANR产生的时候,ActivityManagerService的appNotResponding方法就会被调用,然后在/data/anr/traces.txt文件中写入ANR相关信息.
ANR就不作介绍了,下面只介绍如何分析ANR异常.

通常发生了ANR,ActivityManager会打印报错信息:

E/ActivityManager: ANR in com.rui.android.poc  // ANR出现的进程包名
PID: 1322  // ANR进程ID
Reason: Broadcast of Intent { act=android.intent.action.SCREEN_ON flg=0x50000010 }//导致ANR的原因
Load: 4.13 / 2.52 / 1.05
CPU usage from 0ms to 12843ms later://CPU在ANR发生后的使用情况
98% 1322/com.rui.android.poc: 0.5% user + 98% kernel / faults: 1044 minor 104 major
5.6% 567/system_server: 3.4% user + 2.1% kernel / faults: 1940 minor 217 major
2.3% 766/com.android.systemui: 1.2% user + 1% kernel / faults: 251 minor 165 major
1.4% 268/kworker/0:2: 0% user + 1.4% kernel
1.2% 1690/adbd: 0.1% user + 1% kernel / faults: 2 minor 3 major
1% 756/com.android.phone: 0.6% user + 0.3% kernel / faults: 347 minor 61 major
0.9% 139/surfaceflinger: 0.3% user + 0.5% kernel / faults: 1 minor 5 major
0.2% 929/com.sprd.opm: 0.2% user + 0% kernel / faults: 279 minor 115 major
0.2% 23/kworker/u4:1: 0% user + 0.2% kernel
0.2% 368/kworker/u5:1: 0% user + 0.2% kernel
0.1% 914/com.android.modemassert: 0% user + 0% kernel / faults: 182 minor 66 major
0.1% 44/ksmd: 0% user + 0.1% kernel
0% 909/com.spreadst.validator: 0% user + 0% kernel / faults: 164 minor 39 major
0% 8/rcu_preempt: 0% user + 0% kernel
0% 43/kswapd0: 0% user + 0% kernel
0% 117/mmcqd/0: 0% user + 0% kernel
+0% 1770/kworker/0:3: 0% user + 0% kernel
+0% 1774/debuggerd: 0% user + 0% kernel
97% TOTAL: 3.7% user + 55% kernel + 38% iowait  // CUP占用情况
CPU usage from 12301ms to 12824ms later:
100% 1322/com.rui.android.poc: 0% user + 100% kernel
100% 1322/rui.android.poc: 0% user + 100% kernel
1.9% 1333/Binder_2: 0% user + 1.9% kernel
 3.8% 567/system_server: 1.9% user + 1.9% kernel
3.8% 581/ActivityManager: 1.9% user + 1.9% kernel
1.3% 139/surfaceflinger: 1.3% user + 0% kernel
1.3% 249/DispSync: 0% user + 1.3% kernel
1.5% 766/com.android.systemui: 1.5% user + 0% kernel / faults: 2 minor
1.5% 766/ndroid.systemui: 0% user + 1.5% kernel
1.6% 1770/kworker/0:3: 0% user + 1.6% kernel
100% TOTAL: 0% user + 54% kernel + 45% iowait

Log分析:

log打印了ANR的基本信息,我们可以分析CPU使用率得知ANR的简单情况;如果CPU使用率很高,接近100%,可能是在进行大规模的计算更可能是陷入死循环;如果CUP使用率很低,说明主线程被阻塞了,并且当IOwait很高,可能是主线程在等待I/O操作的完成.
对于ANR只是分析Log很难知道问题所在,我们还需要通过Trace文件分析stack调用情况.

traces.txt是如何生成的

当APP(包括系统APP和用户APP)进程出现ANR、应用响应慢或WatchDog的监视没有得到回馈时,系统会dump此时的top进程,进程中Thread的运行状态就都dump到这个Trace文件中了.每次发生ANR, 这个文件都会被清空,写入新的内容. 如果想查看以前发生ANR的信息, 可以去查看DB文件.

DropBox中的log

traces.txt只保留最后一次发生ANR时的信息, android 2.2开始增加了DropBox功能, 保留历史上发生的所有ANR的log.
“/data/system/dropbox”是DB指定的文件存放位置.
日志保存的最长时间, 默认是3天.

shell@sp9820a_Rui8503:/data # cd system/dropbox/
shell@sp9820a_Rui8503:/data/system/dropbox # ls
SYSTEM_BOOT@1325376052965.txt
system_app_crash@1325376052598.txt
system_app_crash@1325376054905.txt
system_app_strictmode@1325376048453.txt.gz
system_app_strictmode@1325376053442.txt.gz
system_app_strictmode@1325376058904.txt.gz
system_server_wtf@1325376043066.txt
system_server_wtf@1325376052708.txt
system_server_wtf@1325376052842.txt
shell@sp9820a_Rui8503:/data/system/dropbox #

SystemServer在启动时, 会创建并添加DROPBOX_SERVICE.

//SystemServer.java
private void startOtherServices() {
... ...
            try {
                Slog.i(TAG, "DropBox Service");
                ServiceManager.addService(Context.DROPBOX_SERVICE,
                        new DropBoxManagerService(context, new File("/data/system/dropbox")));
            } catch (Throwable e) {
                reportWtf("starting DropBoxManagerService", e);
            }
... ...
}

在traces.txt中可以看到如下信息:

//说明了发生ANR的进程id、时间和进程名称。
----- pid 2023 at 2018-07-24 19:33:17 -----
Cmd line: com.rui.android.poc
//下面三行是线程的基本信息
JNI: CheckJNI is off; workarounds are off; pins=0; globals=348
DALVIK THREADS://以下是各个线程的函数堆栈信息
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

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

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

//此后是线程的调度信息,sysTid是Linux下的内核线程id,nice是线程的调度优先级,
//sched分别标志了线程的调度策略和优先级,cgrp是调度属组,handle是线程的处理函数地址。
  | sysTid=2023 nice=-1 sched=0/0 cgrp=apps handle=1074626900

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

//线程的调用栈信息(这里可查看导致ANR的代码调用流程,分析ANR最重要的信息)
  (native backtrace unavailable)
  at libcore.io.Posix.open(Native Method)
  at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)
  at libcore.io.IoBridge.open(IoBridge.java:430)
  at java.io.FileInputStream.<init>(FileInputStream.java:78)
  at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:58)
  at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:51)
  at com.ruiven.android.Peripheral.util.kingLog.writeToFiles(kingLog.java:71)
  at com.ruiven.android.Peripheral.util.kingLog.f(kingLog.java:47)
  at com.ruiven.android.poc.receiver.ReceiverBatteryChanged.onReceive(ReceiverBatteryChanged.java:114)
  at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:767)
  at android.os.Handler.handleCallback(Handler.java:769)
  at android.os.Handler.dispatchMessage(Handler.java:97)
  at android.os.Looper.loop(Looper.java:136)
  at com.ruiven.android.Peripheral.util.Cockroach$1.run(Cockroach.java:38)
  at android.os.Handler.handleCallback(Handler.java:769)
  at android.os.Handler.dispatchMessage(Handler.java:97)
  at android.os.Looper.loop(Looper.java:136)
  at android.app.ActivityThread.main(ActivityThread.java:5375)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:976)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:792)
  at dalvik.system.NativeStart.main(Native Method)

//Binder线程是进程的线程池中用来处理binder请求的线程
"Binder_3" prio=5 tid=23 NATIVE
  | group="main" sCount=1 dsCount=0 obj=0x41b765f8 self=0x4f213f18
  | sysTid=1347 nice=0 sched=0/0 cgrp=apps handle=1327603952
  | state=S schedstat=( 0 0 0 ) utm=0 stm=3 core=1
  #00  pc 000205d0  /system/lib/libc.so (__ioctl+8)
  #01  pc 0002d01f  /system/lib/libc.so (ioctl+14)
  #02  pc 0001e519  /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+140)
  #03  pc 0001ec67  /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+6)
  #04  pc 0001ecfd  /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
  #05  pc 000236cd  /system/lib/libbinder.so
  #06  pc 0000ea19  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+216)
  #07  pc 0004e769  /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+68)
  #08  pc 0000e54b  /system/lib/libutils.so
  #09  pc 0000d240  /system/lib/libc.so (__thread_entry+72)
  #10  pc 0000d3dc  /system/lib/libc.so (pthread_create+240)
  at dalvik.system.NativeStart.run(Native Method)

//JDWP线程是支持虚拟机调试的线程,daemon表示守护进程,不需要关心
"JDWP" daemon prio=5 tid=4 VMWAIT
  | group="system" sCount=1 dsCount=0 obj=0x41a21868 self=0x4cc821b0
  | sysTid=785 nice=0 sched=0/0 cgrp=apps handle=1257589656
  | state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=1
  #00  pc 00021420  /system/lib/libc.so (recvmsg+8)
  #01  pc 000668ab  /system/lib/libdvm.so
  #02  pc 00066adf  /system/lib/libdvm.so
  #03  pc 000697af  /system/lib/libdvm.so
  #04  pc 00059fdd  /system/lib/libdvm.so
  #05  pc 0000d240  /system/lib/libc.so (__thread_entry+72)
  #06  pc 0000d3dc  /system/lib/libc.so (pthread_create+240)
  at dalvik.system.NativeStart.run(Native Method)

//“Signal Catcher”负责接收和处理kernel发送的各种信号,例如SIGNAL_QUIT、SIGNAL_USR1等就是被该线程
//接收到并处理的,traces.txt 文件中的内容就是由该线程负责输出的,可以看到它的状态是RUNNABLE.
"Signal Catcher" daemon prio=5 tid=3 RUNNABLE
  | group="system" sCount=0 dsCount=0 obj=0x41a21770 self=0x4af3e5e0
  | sysTid=784 nice=0 sched=0/0 cgrp=apps handle=1257677168
  | state=R schedstat=( 0 0 0 ) utm=2 stm=2 core=1
  at dalvik.system.NativeStart.run(Native Method)
----- end 2023 -----

如果ANR在native层,那么堆栈中就不会有相关调用的路径,这种情况只能在native层添加更多的Log,一步步来查找了

NATIVE表示当前线程的状态:

Thread状态及其对应的值

ThreadState (defined at “dalvik/vm/thread.h “)
THREAD_UNDEFINED = -1, /* makes enum compatible with int32_t */
THREAD_ZOMBIE = 0, /* TERMINATED */( 线程死亡,终止运行)
THREAD_RUNNING = 1, /* RUNNABLE or running now */(线程可运行或正在运行)
THREAD_TIMED_WAIT = 2, /* TIMED_WAITING in Object.wait() */(执行了带有超时参数的wait、sleep或join函数)
THREAD_MONITOR = 3, /* BLOCKED on a monitor */(线程阻塞,等待获取对象锁)
THREAD_WAIT = 4, /* WAITING in Object.wait() */( 执行了无超时参数的wait函数)
THREAD_INITIALIZING= 5, /* allocated, not yet running */(新建,正在初始化,为其分配资源)
THREAD_STARTING = 6, /* started, not yet on thread list */(新建,正在启动)
THREAD_NATIVE = 7, /* off in a JNI native method */(正在执行JNI本地函数)
THREAD_VMWAIT = 8, /* waiting on a VM resource */(正在等待VM资源)
THREAD_SUSPENDED = 9, /* suspended, usually by GC or debugger */(线程暂停,通常是由于GC或debug被暂停)                       

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

mutexes(互斥)的选项简写对应含义:
tll--thread list lock
tsl-- thread suspend lock
tscl-- thread suspend count lock
ghl--gc heap lock
hwl--heap worker lock
hwll--heap worker list lock
  • 通过Cmd line: packagename可以找到要分析的进程
  • 并不是trace文件包含的应用就一定是造成ANR的帮凶,应用出现在trace文件中,只能说明出现ANR的时候这个应用进程还活着,trace文件的顶部则是触发ANR的应用信息。因此,如果你的应用出现在了trace文件的顶部,那么很有可能是因为你的应用造成了ANR,否则是你的应用造成ANR的可能性不大,但是具体是不是还需要进一步分析

参考:https://blog.csdn.net/yxz329130952/article/details/50087731

 
posted @ 2022-06-09 17:54  Endv  阅读(1459)  评论(0编辑  收藏  举报