ANR是一套监控Android应用程序响应是否及时的机制,可以把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:
- 埋定时炸弹:system_server进程启动倒计时,在规定时间内如果目标应用进程没有干完所有的活,则system_server会杀进程。
- 拆炸弹:在规定时间内干完所有活,并及时向system_server报告完成,请求解除定时炸弹。
- 引爆炸弹:system_server立即封装现场,抓取快照,搜索目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后炸毁目标。
常见的anr有service,broadcast,provider以及input。
Service超时机制
- 客户端(App进程)向system_server进程发起启动服务的请求
- biner_1线程接收该请求,紧接着向activitymanager线程发送消息,埋下定时炸弹
- binder_1通知service所在进程准备开始干活
- binder_3收到任务后转交给main线程,加入messagequeue
- 完成service启动的生命周期,然后等待sharepreferences持久化
- sp执行完成后,立刻向system_server汇报工作已完成
- system_server的binder_2收到完成工作的汇报后,立刻拆除炸弹。如果在炸弹倒计时结束前拆除炸弹则相安无事,否则引爆炸弹(触发anr)
Broadcast超时机制
- 客户端(App进程)向system_server进程发起发送广播的请求
- biner_1线程接收该请求并转交给activitymanager线程
- activitymanager执行任务(processNextBroadcast方法)的过程埋下定时炸弹
- activitymanager通知receiver进程(上面图片有误,应该是receiver进程)准备干活
- binder_3收到任务后转交给main线程,加入messagequeue
- 完成receiver启动的生命周期,发现当前进程还有sp正在执行写入文件的操作,便将向system_server汇报的任务交给sp(queue-work-looper线程)
- sp完成数据的持久化,便可以向system_server汇报工作完成
- system_server的binder_2收到完成工作的汇报后,立刻拆除炸弹。如果在炸弹倒计时结束前拆除炸弹则相安无事,否则引爆炸弹(触发anr)
注意:sp从8.0开始采用名叫"queue-work-looper"的handle线程,老版本采用newSingleThreadExecutor创建的单线程的线程池。
如果是动态广播,或者静态广播没有正在执行持久化操作的sp任务,则不需要经过"queue-work-looper"线程中转,直接由main线程向system_server汇报工作。
sp的apply将修改的数据项更新到内存,然后再异步同步数据到磁盘文件,因此很多地方会推荐在主线程调用采用apply方式,避免阻塞主线程,但静态广播超时检测过程需要sp全部持久化到磁盘,如果过度使用apply会增大anr的概率。
Provider超时机制
provider的超时是在provider进程首次启动的时候才会检测,在provider进程已启动的场景,再次请求provider并不会触发provider超时。
- 客户端(App进程)向中控系统(system_server进程)发起获取内容提供者的请求
- 中控系统派出一名空闲的通信员(binder_1)接收该请求,检测到内容提供者尚未启动,则先通过zygote孵化新进程
- 新化的provider进程向中控系统注册自己的存在
- 中控系统的通信员2号接收到该信息后,向组件管家(ActivityManager线程)发送消息,埋下炸弹
- 通信员2号通知工地(provider进程)的通信员准备开始干活
- 通讯员4号(binder_4收到任务后转交给包工头(main主线程),加入包工头的任务队列(MessageQueue)
- 包工头经过一番努力干完活(完成provider的安装工作后向中控系统汇报工作已完成
- 中控系统的通讯员3号(binder_3)收到包工头的完工汇报后,立刻拆除炸弹。如果在倒计时结束前拆除炸弹则相安无事,否则会引发爆炸(触发ANR)
input超时机制
input的超时检测机制跟service、broadcast、provider截然不同,为了更好的理解input过程先来介绍两个重要线程的相关工作:
- InputReader线程负责通过EventHub(监听目录/dev/input)读取输入事件,一旦监听到输入事件则放入到InputDispatcher的mlnBoundQueue队列,并通知其处理该事件;
- InputDispatcher线程负责将接收收到的输入事件分发给目标应用窗口,分发过程使用到3个事件队列
- mlnBoundQueue用于记录InputReader发送过来的输入事件;
- outBoundQueue用于记录即将分发给目标应用窗口的输入事件
- waitQueue用于记录已分发给目标应用,且应用尚未处理完成的输入事件;
input的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸,所以更像是扫雷的过程,具体如下图所示。
- InputReader线程通过EventHub监听底层上报的输入事件,一旦收到输入事件则将其放至mlnBoundQueue队列,并唤醒InputDispatcher线程
- InputDispatcher开始分发输入事件,设置埋雷的起点时间。先检测是否有正在处理的事件(mPendingEvent)如果没有则取出mnBoundQueue队头的事件,并将其赋值给mPendingEvent,且重置ANR的timeout;否则不会从mlnBoundQueue中取出事件,也不会重置timeout。然后检查窗口是否就绪
(checkWindowReadyForMorelnputLocked),满足以下任一情况,则会进入扫雷状态(检测前一个正在处理的事件是否超时),终止本轮事件分发,否则继续执行步骤3。- 对于按键类型的输入事件,则outboundQueue或者waitQueue不为空
- 对于非按键的输入事件:则waitQueue不为空,且等待队头时间超时500ms
- 当应用窗口准备就绪,则将mPendingevent转移到outBoundQueue队列
- 当outBoundQueue不为空,且应用管道对端连接状态正常,则将数据从outboundQueue中取出事件,放入aitQueue队列
- InputDispatcher通过socket告知目标应用所在进程可以准备开始干活
- App在初始化时默认已创建跟中控系统双向通信的socketpair,此时App的包工头(main线程)收到输入事件后会层层转发到目标窗口来处理
- 包工头完成工作后,会通过socket向中控系统汇报工作完成,则中控系统会将该事件从waitQueue队列中移除。
input超时机制为什么是扫雷,而非定时爆炸呢?是由于对于input来说即便某次事件执行时间超过timeout时长,只要用户后续在没有再生成输入事件,则不会触发ANR。这里的扫雷是指当前输入系统中正在处理着某个耗时事件的前提下,后续的每一次input事件都会检测前一个正在处理的事件是否超时(进入扫雷状态),检测当前的时间距离上次输入事件分发时间点是否超过timeout时长。
前台与后台服务
系统对前台服务启动的超时为20s,而后台服务超时为200s,那么系统是如何区别前台还是后台服务的?
在startservice过程根据发起方进程所属的进程调度组来决定被启动的服务是属于前台还是后台。当发起方进程不等于ProcessList.SCHED_GROUP_BACKGROUND(后台进程组)则认为是前台服务,否则为后台服务。
进程调度组大体可分为top、前台、后台,进程优先级以及进程调度组算法比较复杂,其对应关系可理解为adj等于0的进程属于top进程组,等于100或200的进程属于前台进程组,>200的进程属于后台进程组,后台进程组对于用户来说基本是无感知的,主要做一些后台工作,分配更少的时间片。
前台服务准确来说,是指由处于前台进程调度组的进程发起的服务。这跟常说的fg-service服务有所不同,fg-service是指挂有前台通知的服务。
前台与后台广播超时
根据发送广播sendBroadcast(Intent intent)中的intent的flags是否包含FLAG_RECEIVER_FOREGROUND来决定把该广播是放入前台广播队列或者后台广播队列,前台广播队列的超时为10s,后台广播队列的超时为60s,默认情况下广播是放入后台广播队列,除非指明加上FLAG_RECEIVER_FOREGROUND标识。
后台广播比前台广播拥有更长的超时值,同时在广播分发过程遇到后台service的启动(mDelayBehindsenvices)会延迟分发广播,等待service的完成,因为等待service而导致的广播ANR会被忽略掉;后台广播属于后台进程调度组,而前台广播属于前台进程调度组。简而言之,后台广播更不容易发生ANR,同时执行的速度也会更慢。
另外,只有串行处理的广播才有超时机制,因为接收者是串行处理的,前一个receiver处理慢,会影响后一个
receiver;并行广播通过一个循环一次性向所有的receiver分发广播事件,所以不存在彼此影响的问题,则没有广播超时。
前台广播准确来说,是指位于前台广播队列的广播
前台与后台ANR
决定是前台还是后台anr取决于该应用发生anr时对用户是否可感知,比如拥有当前前台可见的activity的进程,或者拥有前台通知的fg-service进程,这些是用户可感知的场景;后台anr只抓取发生无响应进程的trace,也不会收集cpu信息,并且会在后台直接杀掉该无响应的进程,前台anr准确来说,是指对用户可感知的进程发生的anr。
后记
- 哪些路径会引发anr?
从埋下定时炸弹到拆炸弹之间的任何一个或多个路径执行慢都会导致anr。 - 发生anr时从trace来看主线程却处于空闲状态或者停留在非耗时代码的原因有哪些?
可以是抓取trace过于耗时而错过现场,可以是主线程消息队列堆积大量消息而最后抓取快照一刻只是瞬时状态,可以是广播的"queue-work-looper"一直在处理sp操作。