ANR
ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多Android系统的设计思想。
1 什么是ANR?
所谓ANR也就是Application not responsing,即应用程序无响应。在Android中,AMS和WMS会监测应用程序的响应时间,如果应用程序主线程在超时时间内对输入事件没有处理完毕或对特定的操作没有执行完毕,就会出现ANR。对于输入事件没有处理完毕的ANR,Android会弹出一个对话框,提示用户当前应用程序没有响应,用户可以选择继续等待或者关闭这个应用程序(也就是杀掉这个进程)。
2 ANR问题的产生
系统原因:由于kernel/Framework/Driver等存在问题,导致系统不稳定最终变现出ANR
应用原因:线程死锁、阻塞或性能低下。需避免将耗时操作放在主线程。
3 ANR问题的分类
InputDispatchingTimedOut :应用程序主线程在5秒内没有完成用户的input事件(比如按键事件、屏幕触摸事件);
Service Timeout :应用程序没有执行完成service的bind/create/start/destroy/unbind操作 前台服务20秒超时,后台服务200秒超时;
Broadcast Timeout :应用程序在规定时间内没有执行完成onReceive操作 前台广播10秒超时,后台广播60秒超时(BROADCAST_FG_TIMEOUT /BROADCAST_FG_TIMEOUT);
Content Provider Timeout :应用程序在20秒内没有执行完成ContentProvider相关操作.
4 ANR问题的分析流程
1)Find ANR time,PID,ANR type
2)Find JE/NE before ANR
3)Check trace.txt mapping process ID and time stamp
4)Check CPU usage
5)Find some more information about main thread through main log and event log
至于上面说的ANR的trace.txt文件,展讯项目一般在log的mic文件夹里的snapshot文件里,MTK项目则需要用GAT工具解析DB文件,其trace信息都在DB中。
分析trace.txt文件一般照着下面的步奏来:
5 ANR是怎么判断超时时间的
怎样定义超时时间、什么时候开始计时、以及如何计时的问题。
ANR超时时间的定义,是在执行者,也就是系统服务那里定义的,这个容易理解。
ANR从什么时候开始计时呢,在Android中,实际上是系统服务在控制每个组件的生命周期回调,所以可以在这个逻辑入口开始计时。
至于如何计时的问题,其实Android系统里已经有一个时间相对准确的机制,就是Handler机制,可以用Handler机制发送延时消息,如果超时了,就发出ANR,如果没有超时,就取消队列里的延时消息,这就解决了计时的问题。
ANR的基本原理如下:
6 各组件触发ANR的过程
会产生ANR的,包括Activity、Service、BroadCastReceiver、ContentProvider和Application,他们各自的ANR过程都有些不同,我们先从简单的看起。
6.1 BroadCastReceiver
时间定义
广播的超时时间是定义在AMS里:
BROADCAST_FG_TIMEOUT:10s
BROADCAST_BG_TIMEOUT:60s
前/后台广播是在发送Intent时,在intent.addFlag里定义的。
触发时机
当AMS处理广播时,会调用processNextBroadcast函数,这里面会处理并行广播和串行广播,其中,并行广播是单向通知,不需要等待反馈,所以并行广播没有ANR。
在处理串行广播时:
首先,判断是否已经有一个广播超时消息;
然后,根据目标进程优先级,分别在前台队列和后台队列(超时时限不同)中排队处理;
接下来,根据不同的队列,发出不同延时的ANR消息;
如果处理及时,取消延时消息;
如果处理超时,触发ANR.
ANR处理
广播的ANR处理相对简单,主要是再次判断是否超时、记录日志,记录ANR次数等。
然后就继续调用processNextBroadcast函数,处理下一条广播了。
6.2 Service
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)函数里,提供了进程信息。
6.3 ContentProvider
ContentProvider也是会ANR的,如果AMS中的ContentProviderClient在处理中超时,也可以启动ANR,超时时间和是否使用,由开发者决定:
CONTENT_PROVIDER_PUBLISH_TIMEOUT:10s
CONTENT_PROVIDER_RETAIN_TIME:20s
6.4 Application
Application的启动是执行在主线程的,attachBaseContext和onCreate等回调也是在主线程的,这里如果出现ANR,会影响到当前组件的运行。
6.5 Activity
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提示框。
7 ANR的处理和日志
ANR是在AMS的appNotResponding函数中处理的,主要是记录日志,和弹出提示。
7.1 如何记录
log日志记录在data/anr/traces.txt文件中,这个文件每次只记录最近的一次ANR,有可能记录失败。
文件内容包括dump栈,CPU负载,IO Wait等
7.2 如何解读
分析ANR,除了检查代码的生命周期函数是否有耗时操作,还可以分析traces日志,分析角度主要包括:
1)栈信息,一般可以知道在哪段代码附近发生了ANR,可能不是直接原因,但一般在问题点附近。
2)CPU用量,看负载比例和平均负载,判断是不是有别的App占用了过多的CPU。
3)IO Wait,看IOWait的占比是否很高,判断是否在等待IO。
7.3 ANR的可能原因
如果从根源上划分的话,导致ANR的原因有如下几点:
1)IO操作,如数据库、文件、网络;
2)CPU不足,一般是别的App占用了大量的CPU,导致App无法及时处理;
3)硬件操作,如camera;
4)线程问题,如主线程被join/sleep,或wait锁等导致超时;
5)service问题,如service忙导致超时无响应,或service binder的数量达到上限;
6)system server问题,如WatchDog发现ANR。
8 UI线程主要包括如下:
1)Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick(),etc
2)AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel,etc
3)Mainthread handler: handleMessage(), post*(runnable r), etc
4)Other
9 如何避免ANR
1)运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2)应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广播时需要向用户展示什么,你应该使用Notification Manager来实现。
4)通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应, 如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件,程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿,使用Systrace和TraceView找出影响响应的问题。
10 ANR的处理
针对三种不同的情况, 一般的处理情况如下
1)主线程阻塞的
开辟单独的子线程来处理耗时阻塞事务。
2)CPU满负荷, I/O阻塞的
I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行。
3)内存不够用的
增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等。
11 需要做哪些层次的工作来避免其发生呢?
哪些地方是执行在主线程的
1)Activity的所有生命周期回调都是执行在主线程的。
2)Service默认是执行在主线程的。
3)BroadcastReceiver的onReceive回调是执行在主线程的。
4)没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的。
5)AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的。
6)View的post(Runnable)是执行在主线程的。