前言
四大组件中的service、Broadcast、ContentProvider他们的生命周期会检测ANR超时,还有Input事件(屏幕触摸事件和键盘数据事件)也会检测到ANR超时。
startService开启一个Service,然后通过binder通知AMS启动Service,AMS经过各种判断后可以启动,然后就会通过binder通知service所在的应用进程创建一个Service同时执行onCreate生命周期,这时AMS会通过Handler发送一个延迟任务(ANR任务)。进程Service生命周期执行完成会通过binder通知AMS,AMS收到通知后就会移除ANR任务。当应用进程处理Service Create的任务超过设定的延迟后,AMS中ANR任务就会执行,然后应用未响应的弹窗。Service、BroadCast和ContentProvider处理ANR的方式类似。
Input事件和其他组件处理方式不一样。
ActivityManager只是AMS在应用进程的一个binder的Client,最终会在AMS#startService()方法中执行。bumpServiceExecutingLocked()就添加了一个ANR延迟任务,调用app.thread.scheduleCreateService()方法,这个thread也是一个binder的Client,它的Server也就是应用进程中的ApplicationThread。scheduleServiceTimeoutLocked()方法会判断是前台和后台Service,前台超时20s,后台超时200s。在serviceDoneExecutingLocked()方法中移除ANR任务。
Input ANR原理
Input流程的代码逻辑主要是C++写的。InputDispatcher在获取到处理事件后通过Socket的通信方式发送给对应的Window来处理,也就是应用进程多对应的Window。发送出去后就会把这个事件添加到waitQueue队列中,只有等到应用进程把这个事件处理完毕了才会通过Socket通知InputDispatcher,应用进程处理Input事件是在应用主线程。InputDispatcher收到已经完成的事件后,会把对应的waitQueue中等待的事件移除。
当waitQueue中最旧的事件超过500ms时,它就认为目前的Window不可用。如果window可用就走事件下发到应用进程逻辑,如果不可用就跳过这次事件下发,然后检查上次事件和下发到当前时间的时间间隔,如果这个间隔超过了5s那就触发ANR,ANR消息会通过jni调用到IMS,然后到AMS,最后执行ANR的处理流程。
在Activity生命周期中阻塞10s都不会出现ANR,因为单纯的Activity的生命周期中,AMS本来就没有做ANR处理,会造成很大程度ANR风险,因为还有其他4种情况下导致ANR,以input事件,当事件到达后,由于主线程被Activity#onCreate种阻塞10s,然后Input任务的Message在MessageQueue种只能等待,等待10s就会导致Input事件的ANR触发。
出现ANR后会发送SIGQUIT信号给应用进程,需要通过C/C++代码监控,有的时候SIGQUIT信号也不一定是ANR。出现ANR后,系统会在data/anr目录下存放出现ANR记录,可以通过adb bugreport命令把日志文件记录下载下来。
ANR问题定位
ANR可能是由于AMS本身处理不过来任务导致,也有可能是CPU本身发热降频导致处理任务能力下降,我们可以先忽略这些原因,从自身应用找原因。当我们监听应用发生ANR时,找当前主线程的方法栈就能定位到导致ANR的罪魁祸首吗?当使用Tread.sleep()测试的时候是可行的,而线上实际情况复杂的多。
当Input事件到来时,主线程的MessageQueue状态(A,B,C),假如Input事件是D,插入MessageQueue(A,B,C,D)。A,B,C他们各自需要执行2s,那么Input处理的事件就需要等待6s才能执行,在D执行前造成ANR(C,D)。这个时候拿主线程方法栈信息,就会定位到C所对应的栈,所以你就认为C所对应的代码有问题?其实是ABC三个任务叠加导致这次ANR。
我们只能尽可能减小主线程的负担从而减少ANR, 和OOM一样,我们无法阻止它的发生。