Android ANR机制

ANR机制
1 ANR概述
ANR(Application Not responding),应用程序无响应。Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间未能得到有效响应或者响应时间过长,都会造成ANR。ANR由消息处理机制保证, 核心原理是消息调度和超时处理,Android在系统层实现了一套精密的机制来发现ANR。
ANR本质上其实是一个性能的问题,它要求主线程在规定的时间内完成一些操作,如果处理超时,则会认为主线程失去了响应其他操作的努力,主线程的耗时操作,都会降低应用程序的响应能力。
ANR机制主体实现在系统层,系统进程设计了不同的超时限制来跟踪消息的处理。所有与ANR相关的消息,都会经过系统进程调度,然后派发到应用进程完成对消息的实际处理。 一旦应用程序处理消息不当,超时限制就起作用了,它会收集一些类似CUP使用情况等信息并报告用户进程无响应情况。
2 各组件触发ANR的过程
ANR是一套监控Android应用响应是否及时的机制,可以把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:

  1. 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
  2. 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
  3. 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。
    2.1 BroadCastReceiver触发ANR
    broadcast跟service超时机制大抵相同,对于静态注册的广播在超时检测过程需要检测SP,如下图所示。
    在这里插入图片描述

图解:
客户端(App进程)向中控系统(system_server进程)发起发送广播的请求中控系统派出一名空闲的通信员(binder_1)接收该请求转交给组件管家(ActivityManager线程)组件管家执行任务(processNextBroadcast方法)的过程埋下定时炸弹组件管家通知工地(receiver所在进程)的通信员准备开始干活通讯员3号(binder_3)收到任务后转交给包工头(main主线程),加入包工头的任务队列(MessageQueue)包工头经过一番努力干完活(完成receiver启动的生命周期),发现当前进程还有SP正在执行写入文件的操作,便将向中控系统汇报的任务交给SP工人(queued-work-looper线程)SP工人历经艰辛终于完成SP数据的持久化工作,便可以向中控系统汇报工作完成中控系统的通讯员2号(binder_2)收到包工头的完工汇报后,立刻拆除炸弹。如果在倒计时结束前拆除炸弹则相安无事,否则会引发爆炸(触发ANR)
如果是动态广播,或者静态广播没有正在执行持久化操作的SP任务,则不需要经过“queued-work-looper”线程中转,而是直接向中控系统汇报,流程更为简单,如下图所示:
在这里插入图片描述

时间定义
广播的超时时间是定义在AMS里:
BROADCAST_FG_TIMEOUT:10s
BROADCAST_BG_TIMEOUT:60s
前/后台广播是在发送Intent时,在intent.addFlag里定义的。

触发时机
当AMS处理广播时,会调用processNextBroadcast函数,这里面会处理并行广播和串行广播,其中,并行广播是单向通知,不需要等待反馈,所以并行广播没有ANR。
在处理串行广播时:
首先,判断是否已经有一个广播超时消息;然后,根据目标进程优先级,分别在前台队列和后台队列(超时时限不同)中排队处理;接下来,根据不同的队列,发出不同延时的ANR消息;如果处理及时,取消延时消息;如果处理超时,触发ANR;
ANR处理
广播的ANR处理相对简单,主要是再次判断是否超时、记录日志,记录ANR次数等。然后就继续调用processNextBroadcast函数,处理下一条广播了。
2.2 Service触发ANR
在这里插入图片描述

图解:
客户端(App进程)向中控系统(system_server进程)发起启动服务的请求中控系统派出一名空闲的通信员(binder_1线程)接收该请求,紧接着向组件管家(ActivityManager线程)发送消息,埋下定时炸弹通讯员1号(binder_1)通知工地(service所在进程)的通信员准备开始干活通讯员3号(binder_3)收到任务后转交给包工头(main主线程),加入包工头的任务队列(MessageQueue)包工头经过一番努力干完活(完成service启动的生命周期),然后等待SharedPreferences(简称SP)的持久化;包工头在SP执行完成后,立刻向中控系统汇报工作已完成中控系统的通讯员2号(binder_2)收到包工头的完工汇报后,立刻拆除炸弹。如果在炸弹倒计时结束前拆除炸弹则相安无事,否则会引发爆炸(触发ANR)
Service真正的管理者是ActiveServices,AMS虽然会去交互与通信,但在启动服务时,是交给ActiveServices去做的。
时间定义
服务的超时时间是定义在AS里:
SERVICE_TIMEOUT:20s;
SERVICE_BACKGROUND_TIMEOUT:200s;
在ActiveService执行startServiceLocked启动服务时,会判断启动服务的发起方的进程(Process.THREAD_GROUP_BG_NONINTERACTIVE),以便选择不同的超时时间。

触发时机
ActivityServices会调用realStartServiceLocked函数启动Service,最前面会先发送一个延迟消息,sendMessageAtTime(msg,time);其中的time,是在最开始startServiceLocked函数中判断出前/后台进程,然后装在ServiceRecord中,一路传过来的。
如果Service操作执行完毕,会执行serviceDoneExecutingLocked,这里面会移除延迟消息。如果Service执行超时,会执行mServices.serviceTimeout。
ANR处理
其实Service的ANR处理也相对简单,记录日志,清理anr活动等。
mServices.serviceTimeout((ProcessRecord)msg.obj)函数里,提供了进程信息。
2.3 ContentProvider触发ANR
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)
ContentProvider也是会ANR的,如果AMS中的ContentProviderClient在处理中超时,也可以启动ANR,超时时间和是否使用,由开发者决定:
CONTENT_PROVIDER_PUBLISH_TIMEOUT:10s
CONTENT_PROVIDER_RETAIN_TIME:20s

  1. 4 Application触发ANR
    Application的启动是执行在主线程的,attachBaseContext和onCreate等回调也是在主线程的,这里如果出现ANR,会影响到当前组件的运行。
    2.5 Avtivity触发ANR
    Activity的ANR是相对最复杂的,也只有Activity中出现的ANR会弹出ANR提示框。
    InputDispatching
    Activity最主要的功能之一是交互,为了方便交互,Android中的InputDispatcher会发出操作事件,最终在Input Manager Service中发出事件,通过InputChannel,向Activity分发事件。
    交互事件必须得到响应,如果不能及时处理,IMS就会报出ANR,交给AMS去弹出ANR提示框。
    KeyDispatching
    如果输入是个Key事件,会从IMS进入ActivityRecord.Token.keyDispatchingTimeOut,然后进入AMS处理,不同的是,在ActivityRecord中,会先截留一次Key的不响应,只有当Key连续第二次处理超时,才会弹出ANR提示框。
    窗口焦点
    Activity总是需要有一个当前窗口来响应事件的,但如果迟迟没有当前窗口(获得焦点),比如在Activity切换时,旧Activity已经onPause,新的Activity一直没有onResume,持续超过5秒,就会ANR。
    App的生命周期太慢,或CPU资源不足,或WMS异常,都可能导致窗口焦点。
    时间定义
    在AMS中定义
    KEY_DISPATCHING_TIMEOUT:5s
    INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT :60s

触发时机
输入界面的触发时机,绝不是尽快提示ANR,而是尽量不提示ANR,因为ANR一旦提示出来,App一般也就关掉了。
对于InputDispatcher来说,如果有新的输入事件时,上一个输入事件还没有处理完,才会通知IMS去判断,是否需要处理ANR。
ANR处理
首先,写日志(data/anr/traces.txt)。
然后,会发出一个Message,弹出ANR提示框。
2.6 Input超时机制
input的超时检测机制跟service、broadcast、provider截然不同,为了更好的理解input过程先来介绍两个重要线程的相关工作
1、InputReader线程负责通过EventHub(监听目录/dev/input)读取输入事件,一旦监听到输入事件则放入到InputDispatcher的mInBoundQueue队列,并通知其处理该事件;
2、InputDispatcher线程负责将接收到的输入事件分发给目标应用窗口,分发过程使用到3个事件队列:
a、mInBoundQueue用于记录InputReader发送过来的输入事件;
b、outBoundQueue用于记录即将分发给目标应用窗口的输入事件;
c、waitQueue用于记录已分发给目标应用,且应用尚未处理完成的输入事件;
input的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸,所以更相信是扫雷的过程,具体如下图所示。
在这里插入图片描述

图解:
InputReader线程通过EventHub监听底层上报的输入事件,一旦收到输入事件则将其放至mInBoundQueue队列,并唤醒InputDispatcher线程InputDispatcher开始分发输入事件,设置埋雷的起点时间。先检测是否有正在处理的事件(mPendingEvent),如果没有则取出mInBoundQueue队头的事件,并将其赋值给mPendingEvent,且重置ANR的timeout;否则不会从mInBoundQueue中取出事件,也不会重置timeout。然后检查窗口是否就绪(checkWindowReadyForMoreInputLocked),满足以下任一情况,则会进入扫雷状态(检测前一个正在处理的事件是否超时),终止本轮事件分发,否则继续执行步骤3对于按键类型的输入事件,则outboundQueue或者waitQueue不为空,对于非按键的输入事件,则waitQueue不为空,且等待队头时间超时500ms当应用窗口准备就绪,则将mPendingEvent转移到outBoundQueue队列当outBoundQueue不为空,且应用管道对端连接状态正常,则将数据从outboundQueue中取出事件,放入waitQueue队列InputDispatcher通过socket告知目标应用所在进程可以准备开始干活App在初始化时默认已创建跟中控系统双向通信的socketpair,此时App的包工头(main线程)收到输入事件后,会层层转发到目标窗口来处理包工头完成工作后,会通过socket向中控系统汇报工作完成,则中控系统会将该事件从waitQueue队列中移除。
input超时机制为什么是扫雷,而非定时爆炸呢?是由于对于input来说即便某次事件执行时间超过timeout时长,只要用户后续在没有再生成输入事件,则不会触发ANR。这里的扫雷是指当前输入系统中正在处理着某个耗时事件的前提下,后续的每一次input事件都会检测前一个正在处理的事件是否超时(进入扫雷状态),检测当前的时间距离上次输入事件分发时间点是否超过timeout时长。如果前一个输入事件,则会重置ANR的timeout,从而不会爆炸。
3 ANR的处理和日志
3.1 日志信息
ANR是在AMS的appNotResponding函数中处理的,主要是记录日志,和弹出提示。
如何记录?
log日志记录在data/anr/traces.txt文件中,这个文件每次只记录最近的一次ANR,有可能记录失败。
文件内容包括dump栈,CPU负载,IO Wait等
如何解读ANR
分析ANR,除了检查代码的生命周期函数是否有耗时操作,还可以分析traces日志,分析角度主要包括:
栈信息,一般可以知道在哪段代码附近发生了ANR,可能不是直接原因,但一般在问题点附近。CPU用量,看负载比例和平均负载,判断是不是有别的App占用了过多的CPU。IO Wait,看IOWait的占比是否很高,判断是否在等待IO。
3.2 获取ANR日志信息
app每次出现anr异常,系统都会记录到手机的traces.txt文件中,所以,出现anr可通过查看traces.txt追踪异常;
获取日志文件操作步骤如下:
①adb shell (链接设备)
②cd /data/anr (进入/data/anr目录下)
③ls (查看当前目录下文件)
④ctrl + d 退出
最后输出日志,adb bugreport /home/mi/hf/log/
输出后具体日志文件路径~/hf/log/FS/data/anr

例子如下:
Activity跳转超时
在这里插入图片描述在这里插入图片描述

获取anr日志文件信息可看到:

4 ANR的原因以及分析
4.1 ANR的原因
如果从根源上划分的话,导致ANR的原因有如下几点:
IO操作,如数据库、文件、网络CPU不足,一般是别的App占用了大量的CPU,导致App无法及时处理硬件操作,如camera线程问题,如主线程被join/sleep,或wait锁等导致超时service问题,如service忙导致超时无响应,或service binder的数量达到上限system server问题,如WatchDog发现ANR
4.2 如何分析ANR问题
ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:
主线程在做一些耗时的工作
主线程被其他线程锁
cpu被其他进程占用,该进程没被分配到足够的cpu资源

判断一个ANR属于哪种情况便是分析ANR问题的关键。拿到一个anr的日志,应该如何分析呢?
在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了/data/anr/traces.txt中,此外,ANR前后该进程打印出的log也有一定价值。一般来说可以按一下思路来分析:
从log中找到ANR反生的信息:可以从log中搜索“ANR in”或“am_anr”,会找到ANR发生的log,该行会包含了ANR的时间、进程、是何种ANR等信息,如果是BroadcastReceiver的ANR可以怀疑BroadCastReceiver.onRecieve()的问题,如果的Service或Provider就怀疑是否其onCreate()的问题。在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:(1). 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。
(2). 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑应用内一些代码不合理消耗掉了CPU资源,如出现了死循环或者后台有许多线程执行任务等等原因,这就要结合trace和ANR前后的log进一步分析了。
(3). 如果CPU总用量不高,该进程和其他进程的占用过高,这有一定概率是由于某些主线程 的操作就是耗时过长,或者是由于主进程被锁造成的。
除了上述的情况(1)以外,分析CPU usage之后,确定问题需要我们进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题最有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:(1). 主线程是running或者native而对应的栈对应了我们应用中的函数,则很有可能就是执行该函数时候发生了超时。
(2). 主线程被block:非常明显的线程被锁,这时候可以看是被哪个线程锁了,可以考虑优化代码。如果是死锁问题,就更需要及时解决了。
(3). 由于抓trace的时刻很有可能耗时操作已经执行完了(ANR -> 耗时操作执行完毕 ->系统抓trace),这时候的trace就没有什么用了,主线程的stack就是这样的:

“main” prio=5 tid=1 Native
| group=“main” sCount=1 dsCount=0 obj=0x757855c8 self=0xb4d76500
| sysTid=3276 nice=0 cgrp=default sched=0/0 handle=0xb6ff5b34
| state=S schedstat=( 50540218363 186568972172 209049 ) utm=3290 stm=1764 core=3 HZ=100
| stack=0xbe307000-0xbe309000 stackSize=8MB
| held mutexes=
kernel: (couldn’t read /proc/self/task/3276/stack)
native: #00 pc 0004099c /system/lib/libc.so (__epoll_pwait+20)
native: #01 pc 00019f63 /system/lib/libc.so (epoll_pwait+26)
native: #02 pc 00019f71 /system/lib/libc.so (epoll_wait+6)
native: #03 pc 00012ce7 /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi+102)
native: #04 pc 00012f63 /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+130)
native: #05 pc 00086abd /system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+22)
native: #06 pc 0000055d /data/dalvik-cache/arm/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+96)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:138)
at android.app.ActivityThread.main(ActivityThread.java:5528)
at java.lang.reflect.Method.invoke!(Native method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:740)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:630)

当然这种情况很有可能是由于该进程的其他线程消耗掉了CPU资源,这就需要分析其他线程的trace以及ANR前后该进程自己输出的log了。
5 如何降低ANR的概率
5.1 如何避免ANR
有一些操作是很危险的,非常容易发生ANR,在写代码时候一定要避免:
1、主线程读取数据:在Android中主线程去读取数据是非常不好的,Android是不允许主线程从网络读数据的,但系统允许主线程从数据库或者其他地方获取数据,但这种操作ANR风险很大,也会造成掉帧等,影响用户体验。
(1). 避免在主线程query provider,首先这会比较耗时,另外这个操作provider那一方的进程如果挂 掉了或者正在启动,我们应用的query就会很长时间不会返回,我们应该在其他线程中执行数据库query、provider的query等获取数据的操作。
(2). sharePreference的调用:针对sharePreference的优化点有很多,文章http://weishu.me/2016/10/13/sharedpreference-advices/ 详细介绍了几点sharepreference使用时候的注意事项。首先sharePreference的commit()方法是同步的,apply()方法一般是异步执行的。所以在主线程不要用其commit(),用apply()替换。其次sharePreference的写是全量写而非增量写,所以尽量都修改完同一apply,避免改一点apply一次(apply()方法在Activity stop的时候主线程会等待写入完成,提交多次就很容易卡)。并且存储文本也不宜过大,这样会很慢。另外,如果写入的是json或者xml,由于需要加和删转义符号,速度会比较慢。
2、 不要在broadcastReciever的onRecieve()方法中干活,这一点很容易被忽略,尤其应用在后台的时候。为避免这种情况,一种解决方案是直接开的异步线程执行,但此时应用可能在后台,系统优先级较低,进程很容易被系统杀死,所以可以选择开个IntentService去执行相应操作,即使是后台Service也会提高进程优先级,降低被杀可能性。
3、各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲,应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR。
4、尽量避免主线程的被锁的情况,在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的ANR风险,对于这种情况有时也可以用异步线程来执行相应的逻辑。另外, 我们要避免死锁的发生(主线程被死锁基本就等于要发生ANR了)。
5.2 如何避免ANR重要
1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
总结:anr异常也是在程序中自己经常遇到的问题,主要的解决办法自己最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现,比如采用Handler+mesage的方式,或者是有时候需要做一些和网络相互交互的耗时操作就采用asyntask异步任务的方式(它的底层其实Handler+mesage有所区别的是它是线程池)等,在主线程中更新UI。
6 触发ANR实例
6.1 Service触发Anr
Service Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。
对于Service有两类:
对于前台服务,则超时为SERVICE_TIMEOUT = 20s;对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
由变量ProcessRecord.execServicesFg来决定是否前台启动
6.1.1 埋炸弹
其中在Service进程attach到system_server进程的过程中会调用realStartServiceLocked()方法来埋下炸弹.
AS.realStartServiceLocked
[-> ActiveServices.java]
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {

//发送delay消息(SERVICE_TIMEOUT_MSG),【见小节2.1.2】
bumpServiceExecutingLocked(r, execInFg, “create”);
try {

//最终执行服务的onCreate()方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {

}
}

As.bumpServiceExecutingLocked
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {

scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
long now = SystemClock.uptimeMillis();
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;

//当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程【见2.3.1】
mAm.mHandler.sendMessageAtTime(msg,
    proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));

}

该方法的主要工作发送delay消息(SERVICE_TIMEOUT_MSG). 炸弹已埋下, 我们并不希望炸弹被引爆, 那么就需要在炸弹爆炸之前拆除炸弹.
6.1.2 拆炸弹
在system_server进程AS.realStartServiceLocked()调用的过程会埋下一颗炸弹, 超时没有启动完成则会爆炸. 那么什么时候会拆除这颗炸弹的引线呢? 经过Binder等层层调用进入目标进程的主线程handleCreateService()的过程.
AT.handleCreateService
[-> ActivityThread.java]
private void handleCreateService(CreateServiceData data) {

java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();

    try {
        //创建ContextImpl对象
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);
        //创建Application对象
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManagerNative.getDefault());
        //调用服务onCreate()方法 
        service.onCreate();

        //拆除炸弹引线[见小节2.2.2]
        ActivityManagerNative.getDefault().serviceDoneExecuting(
                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
    } catch (Exception e) {
        ...
    }
}

在这个过程会创建目标服务对象,以及回调onCreate()方法, 紧接再次经过多次调用回到system_server来执行serviceDoneExecuting.
AS.serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {

if (r.executeNesting <= 0) {
if (r.app != null) {
r.app.execServicesFg = false;
r.app.executingServices.remove®;
if (r.app.executingServices.size() == 0) {
//当前服务所在进程中没有正在执行的service
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);

}

}

该方法的主要工作是当service启动完成,则移除服务超时消息SERVICE_TIMEOUT_MSG。
6.1.3 引爆炸弹
前面介绍了埋炸弹和拆炸弹的过程, 如果在炸弹倒计时结束之前成功拆卸炸弹,那么就没有爆炸的机会, 但是世事难料. 总有些极端情况下无法即时拆除炸弹,导致炸弹爆炸, 其结果就是App发生ANR. 接下来,带大家来看看炸弹爆炸的现场:
在system_server进程中有一个Handler线程, 名叫”ActivityManager”.当倒计时结束便会向该Handler线程发送 一条信息SERVICE_TIMEOUT_MSG,
MainHandler.handleMessage
[-> ActivityManagerService.java ::MainHandler]
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case SERVICE_TIMEOUT_MSG: {

//【见小节2.3.2】
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;

}

}
}

AS.serviceTimeout
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;

synchronized(mAm) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    final long now = SystemClock.uptimeMillis();
    final long maxTime =  now -
            (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    ServiceRecord timeout = null;
    long nextTime = 0;
    for (int i=proc.executingServices.size()-1; i>=0; i--) {
        ServiceRecord sr = proc.executingServices.valueAt(i);
        if (sr.executingStart < maxTime) {
            timeout = sr;
            break;
        }
        if (sr.executingStart > nextTime) {
            nextTime = sr.executingStart;
        }
    }
    if (timeout != null && mAm.mLruProcesses.contains(proc)) {
        Slog.w(TAG, "Timeout executing service: " + timeout);
        StringWriter sw = new StringWriter();
        PrintWriter pw = new FastPrintWriter(sw, false, 1024);
        pw.println(timeout);
        timeout.dump(pw, " ");
        pw.close();
        mLastAnrDump = sw.toString();
        mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
        mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
        anrMessage = "executing service " + timeout.shortName;
    }
}

if (anrMessage != null) {
    //当存在timeout的service,则执行appNotResponding
    mAm.appNotResponding(proc, null, null, false, anrMessage);
}

}

其中
其中anrMessage的内容为”executing service [发送超时serviceRecord信息]”;
6.2 BroadcastReceiver触发ANR
BroadcastReceiver Timeout是位于”ActivityManager”线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。
对于广播队列有两个: foreground队列和background队列:
对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s
6.2.1 埋炸弹
通过调用 processNextBroadcast来处理广播.其流程为先处理并行广播,再处理当前有序广播,最后获取并处理下条有序广播.
rocessNextBroadcast
[-> BroadcastQueue.java]
final void processNextBroadcast(boolean fromMsg) {
synchronized(mService) {

//part 2: 处理当前有序广播
do {
r = mOrderedBroadcasts.get(0);
//获取所有该广播所有的接收者
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
if (mService.mProcessesReady && r.dispatchTime > 0) {
long now = SystemClock.uptimeMillis();
if ((numReceivers > 0) &&
(now > r.dispatchTime + (2mTimeoutPeriodnumReceivers))) {
//当广播处理时间超时,则强制结束这条广播【见小节3.3.2】
broadcastTimeoutLocked(false);

}
}
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
if (r.resultTo != null) {
//处理广播消息消息
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId);
r.resultTo = null;
}
//拆炸弹【见小节3.2.2】
cancelBroadcastTimeoutLocked();
}
} while (r == null);

    //part 3: 获取下条有序广播
    r.receiverTime = SystemClock.uptimeMillis();
    if (!mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mTimeoutPeriod;
        //埋炸弹【见小节3.1.2】
        setBroadcastTimeoutLocked(timeoutTime);
    }
    ...
}

}

对于广播超时处理时机:
首先在part3的过程中setBroadcastTimeoutLocked(timeoutTime) 设置超时广播消息;
然后在part2根据广播处理情况来处理:
当广播接收者等待时间过长,则调用broadcastTimeoutLocked(false);
当执行完广播,则调用cancelBroadcastTimeoutLocked;

setBroadcastTimeoutLocked
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}

设置定时广播BROADCAST_TIMEOUT_MSG,即当前往后推mTimeoutPeriod时间广播还没处理完毕,则进入广播超时流程。
6.2.2 拆炸弹
broadcast跟service超时机制大抵相同,但有一个非常隐蔽的技能点,那就是通过静态注册的广播超时会受SharedPreferences(简称SP)的影响。
sendFinished
关于广播是否考虑SP的情况取决于如下代码:
public final void finish() {
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
if (QueuedWork.hasPendingWork()) {
//当SP有未同步到磁盘的工作,则需等待其完成,才告知系统已完成该广播
QueuedWork.queue(new Runnable() {
public void run() {
sendFinished(mgr);
}
}, false);
} else {
sendFinished(mgr);
}
} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
final IActivityManager mgr = ActivityManager.getService();
sendFinished(mgr);
}
}

可见,只有XML静态注册的广播超时检测过程会考虑是否有SP尚未完成,动态广播并不受其影响。
cancelBroadcastTimeoutLocked
final void cancelBroadcastTimeoutLocked() {
if (mPendingBroadcastTimeoutMessage) {
mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
mPendingBroadcastTimeoutMessage = false;
}
}

移除广播超时消息BROADCAST_TIMEOUT_MSG
6.2.3 引爆炸弹
BroadcastHandler.handleMessage
[-> BroadcastQueue.java ::BroadcastHandler]
private final class BroadcastHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case BROADCAST_TIMEOUT_MSG: {
synchronized (mService) {
//【见小节3.3.2】
broadcastTimeoutLocked(true);
}
} break;

}

}
}

broadcastTimeoutLocked
[-> BroadcastRecord.java]
//fromMsg = true
final void broadcastTimeoutLocked(boolean fromMsg) {
if (fromMsg) {
mPendingBroadcastTimeoutMessage = false;
}

if (mOrderedBroadcasts.size() == 0) {
    return;
}

long now = SystemClock.uptimeMillis();
BroadcastRecord r = mOrderedBroadcasts.get(0);
if (fromMsg) {
    if (mService.mDidDexOpt) {
        mService.mDidDexOpt = false;
        long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
        setBroadcastTimeoutLocked(timeoutTime);
        return;
    }

    if (!mService.mProcessesReady) {
        return; //当系统还没有准备就绪时,广播处理流程中不存在广播超时
    }

    long timeoutTime = r.receiverTime + mTimeoutPeriod;
    if (timeoutTime > now) {
        //如果当前正在执行的receiver没有超时,则重新设置广播超时
        setBroadcastTimeoutLocked(timeoutTime);
        return;
    }
}

BroadcastRecord br = mOrderedBroadcasts.get(0);
if (br.state == BroadcastRecord.WAITING_SERVICES) {
    //广播已经处理完成,但需要等待已启动service执行完成。当等待足够时间,则处理下一条广播。
    br.curComponent = null;
    br.state = BroadcastRecord.IDLE;
    processNextBroadcast(false);
    return;
}

r.receiverTime = now;
//当前BroadcastRecord的anr次数执行加1操作
r.anrCount++;

if (r.nextReceiver <= 0) {
    return;
}
...

Object curReceiver = r.receivers.get(r.nextReceiver-1);
//查询App进程
if (curReceiver instanceof BroadcastFilter) {
    BroadcastFilter bf = (BroadcastFilter)curReceiver;
    if (bf.receiverList.pid != 0
            && bf.receiverList.pid != ActivityManagerService.MY_PID) {
        synchronized (mService.mPidsSelfLocked) {
            app = mService.mPidsSelfLocked.get(
                    bf.receiverList.pid);
        }
    }
} else {
    app = r.curApp;
}

if (app != null) {
    anrMessage = "Broadcast of " + r.intent.toString();
}

if (mPendingBroadcast == r) {
    mPendingBroadcast = null;
}

//继续移动到下一个广播接收者
finishReceiverLocked(r, r.resultCode, r.resultData,
        r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();

if (anrMessage != null) {
    // [见小节3.3.3]
    mHandler.post(new AppNotResponding(app, anrMessage));
}

}

  1. mOrderedBroadcasts已处理完成,则不会anr;
  2. 正在执行dexopt,则不会anr;
  3. 系统还没有进入ready状态(mProcessesReady=false),则不会anr;
  4. 如果当前正在执行的receiver没有超时,则重新设置广播超时,不会anr;
    AppNotResponding
    [-> BroadcastQueue.java]
    private final class AppNotResponding implements Runnable {

    public void run() {
    // 进入ANR处理流程
    mService.appNotResponding(mApp, null, null, false, mAnnotation);
    }
    }

6.3 ContentProvider
ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。
ContentProvider 超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s. 这个跟前面的Service和BroadcastQueue完全不同, 由Provider进程启动过程相关.
6.3.1 埋炸弹
埋炸弹的过程 其实是在进程创建的过程,进程创建后会调用attachApplicationLocked()进入system_server进程.
AMS.attachApplicationLocked
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
ProcessRecord app;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid); // 根据pid获取ProcessRecord
}
}

//系统处于ready状态或者该app为FLAG_PERSISTENT进程则为true
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

//app进程存在正在启动中的provider,则超时10s后发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
    Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
    msg.obj = app;
    mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}

thread.bindApplication(...);
...

}

10s之后引爆该炸弹
6.3.2 拆炸弹
当provider成功publish之后,便会拆除该炸弹.
AMS…publishContentProviders
public final void publishContentProviders(IApplicationThread caller, List providers) {

synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);

   final int N = providers.size();
   for (int i = 0; i < N; i++) {
       ContentProviderHolder src = providers.get(i);
       ...
       ContentProviderRecord dst = r.pubProviders.get(src.info.name);
       if (dst != null) {
           ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);

           mProviderMap.putProviderByClass(comp, dst); //将该provider添加到mProviderMap
           String names[] = dst.info.authority.split(";");
           for (int j = 0; j < names.length; j++) {
               mProviderMap.putProviderByName(names[j], dst);
           }

           int launchingCount = mLaunchingProviders.size();
           int j;
           boolean wasInLaunchingProviders = false;
           for (j = 0; j < launchingCount; j++) {
               if (mLaunchingProviders.get(j) == dst) {
                   //将该provider移除mLaunchingProviders队列
                   mLaunchingProviders.remove(j);
                   wasInLaunchingProviders = true;
                   j--;
                   launchingCount--;
               }
           }
           //成功pubish则移除该消息
           if (wasInLaunchingProviders) {
               mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
           }
           synchronized (dst) {
               dst.provider = src.provider;
               dst.proc = r;
               //唤醒客户端的wait等待方法
               dst.notifyAll();
           }
           ...
       }
   }

}
}

6.3.2 引爆炸弹
在system_server进程中有一个Handler线程, 名叫”ActivityManager”.当倒计时结束便会向该Handler线程发送 一条信息CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG,
MainHandler.handleMessage
[-> ActivityManagerService.java ::MainHandler]
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {

ProcessRecord app = (ProcessRecord)msg.obj;
synchronized (ActivityManagerService.this) {
//【见小节4.3.2】
processContentProviderPublishTimedOutLocked(app);
}
} break;

}

}
}

AMS.processContentProviderPublishTimedOutLocked
private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
cleanupAppInLaunchingProvidersLocked(app, true);
removeProcessLocked(app, false, true, “timeout publishing content providers”);
}

AMS.cleanupAppInLaunchingProvidersLocked
boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
boolean restart = false;
for (int i = mLaunchingProviders.size() - 1; i >= 0; i–) {
ContentProviderRecord cpr = mLaunchingProviders.get(i);
if (cpr.launchingApp == app) {
if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {
restart = true;
} else {
//移除死亡的provider
removeDyingProviderLocked(app, cpr, true);
}
}
}
return restart;
}

对于stable类型的provider(即conn.stableCount > 0),则会杀掉所有跟该provider建立stable连接的非persistent进程.
对于unstable类的provider(即conn.unstableCount > 0),并不会导致client进程被级联所杀.

AMS.removeProcessLocked
private final boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, boolean allowRestart, String reason) {
final String name = app.processName;
final int uid = app.uid;

//移除mProcessNames中的相应对象
removeProcessNameLocked(name, uid);
if (mHeavyWeightProcess == app) {
    mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
            mHeavyWeightProcess.userId, 0));
    mHeavyWeightProcess = null;
}
boolean needRestart = false;
if (app.pid > 0 && app.pid != MY_PID) {
    int pid = app.pid;
    synchronized (mPidsSelfLocked) {
        mPidsSelfLocked.remove(pid);
        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
    }

    ...
    boolean willRestart = false;
    if (app.persistent && !app.isolated) {
        if (!callerWillRestart) {
            willRestart = true;
        } else {
            needRestart = true;
        }
    }
    app.kill(reason, true); //杀进程
    handleAppDiedLocked(app, willRestart, allowRestart);
    if (willRestart) {
        removeLruProcessLocked(app);
        addAppLocked(app.info, false, null /* ABI override */);
    }
} else {
    mRemovedProcesses.add(app);
}
return needRestart;

}

6.4 总结
当出现ANR时,都是调用到AMS.appNotResponding()方法
Timeout时长
对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s;
ContentProvider超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;

超时检测
Service超时检测机制:
超过一定时间没有执行完相应操作来触发移除延时消息,则会触发anr;
BroadcastReceiver超时检测机制:
有序广播的总执行时间超过 2* receiver个数 * timeout时长,则会触发anr;
有序广播的某一个receiver执行过程超过 timeout时长,则会触发anr;

另外:
对于Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding;
对于provider,在其进程启动时publish过程可能会出现ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR的对话框. appNotRespondingViaProvider()过程会走appNotResponding(), 这个就不介绍了,很少使用,由用户自定义超时时间.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北境王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值