anr问题的处理技巧
hi, 大家好,我是爱吃香蕉的猴子,今天记录一下ANR问题的处理套路吧,这也是自己分析ANR问题一般的思路;
- 根据测试的描述,区分是Monkey测试偶现的 ,还是可以手动复现,一般情况前者多一些,我也是根据前者写这个记录;
- 首先,要会区分ANR类型,是什么类型的ANR ??
- 用户输入事件处理超时: KeyDispatchTimeout-主要类型按键或触摸事件,input事件在5S内没有处理完成发生ANR
- 关键字:Reason: Input dispatching timed out xxxx
- Broadcast超时: BroadcastReceiver onReceiver处理事务时前台广播在10S内,后台广播在60s内没有处理完成发生ANR
- 关键字:Timeout of broadcast XXX/Receiver during timeout:XXX/Broadcast of XXX
- Service超时:ServiceTimeout-bind,create,start,unbind等在主线程处理耗时,前台Service在20s内,后台Service在200s内没有处理完成发生ANR
- 关键字:Timeout executing service:/executing service XXX
- ContentProvider超时: publish在10s内没有处理完成发生ANR
-
- 关键字:timeout publishing content providers
- 用户输入事件处理超时: KeyDispatchTimeout-主要类型按键或触摸事件,input事件在5S内没有处理完成发生ANR
- 补充一种类型 窗口获取焦点超时(input 类型的一种子类型)
- 关键字:Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)需要注意区分同为Input dispatching timed out大类的用户输入事件处理超时,这两类超时括号内的提示语是不同的。
- 这个问题,我可以简单理解为窗口切换时,A --> B, 开始焦点在A 窗口在A ,后面 焦点在A 这时窗口时null,如果这个时候超过了5s,那就会发生anr.
查看现场
- ag “am_anr” / ag ag “ANR in” # 这个时候,你会发现很多或者几个anr的地方, 我先看时间最早的,我这里就不贴log了
- 确定最早发生的anr的时间,进程,(如果还没有确定anr类型) 这里基本也会看到特定类型anr的信息
- 然后既然确定了一个anr的位置,那我们就要分析,这个是怎么造成的?? 猜测思路如下
- 1. 主线程耗时操作,如复杂的layout,庞大的for循环,IO等
- 2. 主线程被子线程同步锁block
- 3. 主线程被Binder 对端block
- 4. Binder被占满导致主线程无法和SystemServer通信
- 5. 得不到系统资源(CPU/RAM/IO)(有一个关键搜索:CPU usage)
- 如果,并没有有效的收获,那就检索 上面的anr类型的关键字grep -v ‘mmm|nnn’ abc.txt
看anr的trace文件 我一般是大概有了了解,最后看trace文件
- 匹配一下,上面看到的时间 进程 线程(如果不匹配,也可以反向去找)
- 补充一下:
- main tid=1 //主线程
- group: 线程组名称
- sCount: suspendCount个数
- dsCount: debugSuspendCount个数
- obj: 0x4025b1b8 线程java对象地址
- self: 线程native的对象地址
- Binder Thread #2: Binder线程是进程的线程中用来处理binder请求的线程.
- SUSPENDED:线程暂停,可能是由于输出Trace、GC或debug被暂停
- NATIVE: 正在执行JNI本地函数
- MONITOR: 线程阻塞,等待获取对象锁
- WAIT: 执行了无限等待的wait函数
- TIMED_WAIT: 执行了带有超时参数的wait、sleep或join函数
- VMWAIT: 正在等待VM资源
- RUNNING/RUNNABLE: 线程可运行或正在运行
- INITALIZING: 新建,正在初始化,为其分配资源
- STARTING: 新建,正在启动
- ZOMBIE: 线程死亡,终止运行,等待父线程回收它
- 一般我们要首先查看main线程,是否出现 block wait suspend ,有没有线程间的死锁,有没有出现binder调用服务端没有响应,有没有阻塞在system server.
- 死锁:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。看一个android中的简单Log
Cmd line: com.android.inputmethod.latin
"main" prio=5 tid=1 NATIVE
......
at android.os.BinderProxy.transact(Native Method)
at com.android.internal.view.IInputMethodManager$Stub$Proxy.getCurrentInputMethodSubtype(IInputMethodManager.java:908) ......
Cmd line: system_server
"main" prio=5 tid=1 MONITOR
- waiting to lock <0x41c5a650> (a java.lang.Object) held by tid=12 (WindowManager)......
"WindowManager" prio=5 tid=12 MONITOR
- waiting to lock <0x41c0c118> (a android.view.inputmethod.InputMethodManager$H) held by tid=61 (Binder_F)......
"Binder_F" prio=5 tid=61 MONITOR
at com.android.server.InputMethodManagerService.getCurrentInputMethodSubtype(InputMethodManagerService.java:~3078)
片
- 解析一下:main等待WindowManager ,WindowManager 又等待Binder_F。因此inputmethod主线程的远程调用无法返回,导致ANR。
- 还有一点,这里查看这个cpu占用信息
11 CPU usage from 9818ms to 0ms ago (2020-12-09 06:03:39.534 to 2020-12-09 06:03:49.352):
12 109% 10198/system_server: 85% user + 24% kernel / faults: 12620 minor 11 major
13 35% 2724/pulseaudio: 34% user + 1.7% kernel
14 13% 10545/com.android.car: 10% user + 3.1% kernel / faults: 3518 minor
............
110 74% TOTAL: 49% user + 24% kernel + 0.1% iowait + 0.1% softirq
- binder调用服务端没有响应: 基本通过clent和server共同的方法去grep,看服务端在做什么?
- system server出现block
- 有时候,也可以根据:id threadid 进行grep看执行了什么程序,例如:1089 1089
- 其实,74%这个是负载,这个负载是算是高,在这个负载的情况出现anr也是有可能是系统性能问题,但不能明确,起码我们还要找到什么原因造成的负载高。cpu其他进程占用信息,基本都相对容易判断。
- 其实还有一些分析策略,但最常用的还是这些套路吧,有了好的且可以分享的案例,我会补充上来。
补充:同步锁的内容
public int helloWorld(){
synchronized(this) {
//..
}
}
public synchronized int helloWrld(){
//..
}
上面两种写法其实是一样的,锁的内容都是(this), 都是对象锁;
Code的搬运工V1.0
增加死锁的处理方案:
推荐文章:
ReentrantLock
讨论死锁
reentrant 英[riːˈɛntrənt]
对比
记录细节①:
不同的线程中,不同的对象访问同一个类的对象锁,可以同时进行,不会出现死锁;
记录细节②:
死锁的处理方案:
1、 从项目的Code逻辑上保证顺序执行;
2、 加锁时限,线程尝试获取锁,限制一定的时限,到了时限 则放弃;
3、死锁监听, 类似watchdog的逻辑实现;