Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。
Android的ANR主要有两种方式触发:
1,通过handler的延迟机制触发ANR。
2,Input事件触发ANR。
Service、BroadcastReceiver、ContentProvider都是通过handler的延时机制触发ANR。
以下四个条件都可以造成ANR发生:
1,BroadcastQueue Timeout
在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
2,Service Timeout
前台服务20秒内,后台服务在200秒内没有执行完毕。
3,ContentProvider Timeout
ContentProvider的publish在10s内没进行完。
4,Input Dispatching Timeout
5秒内无法响应屏幕触摸事件或键盘输入事件。
在startService过程根据发起方进程callerApp所属的进程调度组来决定被启动的服务是属于前台还是后台。当发起方进程不等于ProcessList.SCHED_GROUP_BACKGROUND(后台进程组)则认为是前台服务,否则为后台服务,并标记在ServiceRecord的成员变量createdFromFg。简单来说就是Adj>200的进程对用户来说基本是无感知,主要是做一些后台工作,故后台服务拥有更长的超时阈值,同时后台服务属于后台进程调度组,相比前台服务属于前台进程调度组,分配更少的CPU时间片。
1, APP与AMS之间的Binder通信
2,Service ANR机制
3,Broadcast超时检测
如果是动态广播,或者静态广播没有正在执行持久化操作的SP任务,则不需要经过“queued-work-looper”线程中转。根据发送广播sendBroadcast(Intent intent)中的intent的flags是否包含FLAG_RECEIVER_FOREGROUND来决定把该广播是放入前台广播队列或者后台广播队列,前台广播队列的超时为10s,后台广播队列的超时为60s,默认情况下广播是放入后台广播队列,除非指明加上FLAG_RECEIVER_FOREGROUND标识。
4, ContentProvider 超时检测
provider的超时是在provider进程首次启动的时候才会检测,当provider进程已启动的场景,再次请求provider并不会触发provider超时。
5, Input超时机制
input的超时检测机制跟service、broadcast、provider截然不同,为了更好的理解input过程先来介绍两个重要线程的相关工作:
InputReader线程负责通过EventHub(监听目录/dev/input)读取输入事件,一旦监听到输入事件则放入到InputDispatcher的mInBoundQueue队列,并通知其处理该事件;
InputDispatcher线程负责将接收到的输入事件分发给目标应用窗口,分发过程使用到3个事件队列:
a. mInBoundQueue用于记录InputReader发送过来的输入事件;
b. outBoundQueue用于记录即将分发给目标应用窗口的输入事件;
c. waitQueue用于记录已分发给目标应用,且应用尚未处理完成的输入事件;
input的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸。表现就是我们在UI界面操作即使某个时间超过阈值也不会提示,而是要到下次事件才会提示ANR。
对于四大组件发生ANR后,AMS会马上去抓取现场的信息,用于调试分析。收集的信息包括如下:
1,将am_anr信息输出到EventLog,也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息。
2,收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件:
a, 当前发生ANR的进程,system_server进程以及所有persistent进程
b, audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程
c, CPU使用率排名前5的进程
3,将发生ANR的reason以及CPU使用情况信息输出到main log,或/data/anr/目录。
4,将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录。
5,对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉。
应用原因
1,主线程阻塞:如死循环、主线程处理IO时间太长、处理大数据 。
2,主线程锁住:主线程等待子线程的锁 。
3,服务端无法及时响应:服务的线程池有限,如果短时间内client过多可能导致ANR问题。
系统原因
1,CPU被抢占:比如前台在玩游戏,可能会导致你的后台广播出现ANR。
2,内存紧张:系统长期处于内存紧张,会导致频繁内存交换、回收,进而导致应用的一些操作超时。
3,CPU调度不合理:需要检查CPU调度。
4,IO紧张:系统整体IO紧张,造成平时可以很快完成的IO操作出现较长耗时。
Other issue 可能是I/O,内存,CPU,或启动其他模块的问题。
1,ANR LOG筛选
KeyDispatch Timeout : “dispatching timed out”
Broadcast Timeout: “Timeout of broadcast BroadcastRecord”
Service Timeout: “Timeout executing service”
Content Provider Timeout: “timeout publishing content providers”
2,ANR trace关键字解析
sCount 此线程被挂起的次数
dsCount 线程被调试器挂起的次数
prio=5 priority 优先级
nice 线程调度的优先级
utm 线程用户态下使用的时间值(单位是jiffies)
stm 内核态下的调度时间值
core 最后执行这个线程 cpu核的序号
cgrp=default 线程调度组
sched=0/0 线程调度策略和优先级
线程处理函数地址:handle=0x7f85742548
3,其他日志关键字
关键字 |
说明 |
CPU usage from |
筛选CPU信息确认CPU是否被占用 |
Free RAM: |
cached pss 与 cached kernel 任一较低预示内存不足 |
am_meminfo: [1175105536,。。。] |
四个值分别是:Cached, Free, Zram, Kernel, Native |
lowmemorykiller: |
内存不足时触发Low Memory Killer |
FAILED BINDER TRANSACTION |
可能是资源不足导致binder通信出错 |
IPCThreadState: binder thread pool (4 threads) starved for 10018 ms |
可能是资源不足导致binder通信缓慢 |
Davey Displayed Choreographer |
这些是性能相关的关键字,也可能预示着CPU紧张 |
1,主线程处于等待状态
上述主线程堆栈是一个很正常的空闲堆栈,主线程正在等待新的消息。如果ANR日志里主线程是这样一个状态,那可能有两个原因:
a.该ANR是CPU抢占或内存紧张等其他因素引起。
b.这份ANR日志抓取的时候,主线程已经恢复正常。
c.系统处于关机、休眠等特殊场景。
遇到这种空闲堆栈,可以先确认设备场景,然后去分析CPU、内存的情况。
2,lock up导致ANR
以上是典型的abba锁导致的死锁问题。具体修复方案要根据实际场景一般来说,处理死锁问题有三种方法:
a.通过协议来预防或避免死锁,确保系统不会进入死锁状态。
b.可以允许系统进入死锁状态,然后检测它,并加以恢复。
c.可以忽视这个问题,认为死锁不可能在系统内发生。
3,binder对端返回结果慢
对于关键字:
libbinder.so (android::IPCThreadState::talkWithDriver(bool)
通常是由于binder对端返回超时,需要对端检查原因。也有可能是对端binder线程池耗尽。
如果对端不能保证结果返回时限,本应用最好把该消息处理函数的binder调用改为异步模式。
如果要确认对端是谁,则可以通过如下命令打开binder的transaction调试后重新复现:
echo 512 > /sys/module/binder/parameters/debug_mask |
也可在现场打印:
cat /d/binder/transaction_log |
4,CPU不足,IPC复杂导致对端返回慢
5,内存不足,kswapd很高