目录
ANR 概念
超时检测机制
如何避免 ANR 问题?
ANR 分析
ANR 问题线上监控
ANR 概念
ANR(Application Not Response),是指应用程序未响应,Android 系统对于一些事情需要在一定时间范围内完成,如果超过预定时间未能得到有效响应或者响应时间过长,都会造成 ANR。
在 Android 里,应用程序的响应是由 Activty Manager 和 WindowManager 系统服务监视的。当它监测到以下情况中的一个时,Android 就会针对特定的应用程序显示 ANR:
Service Timeout
BroadcastQueue Timeout
ContentProvider Timeout
InputDispatching Timeout
Timeout 时长
对于前台服务,超时则为:SERVICE_TIMEOUT = 20s;
对于后台服务,超时则为:SERVICE_BACKGROUND_TIMEOUT = 200s;
对于前台广播,超时则为:BROADCAST_FG_TIMEOUT = 10s;
对于后台广播,则超时为:BROADCAST_BG_TIMEOUT = 60s;
ContentProvider 超时为 CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;
InputDispatching Timeout: 输入事件分发超时 5s ,包括按键和触摸事件
注意:Input 的超时机制与其它的不同,对于 Input 来说即便某次事件执行时间超过 timeout 时长,只要用户后续没有再生成输入事件,则不会触发 ANR。
1. Service 超时检测机制:
超过一定时间没有执行完相应操作来触发移除延时信息,则会触发 ANR;
2. BroadcastReceiver 超时检测机制:
有序广播的总执行时间超过 2*receiver 个数 * timeout 时长,则会触发 ANR;
有序广播的某一个 receiver 执行过程超过 timeout 时长,则会触发ANR;
3. 另外:
对于 Service,Broadcast,input 发生 ANR 之后,最终都会调用 AMS.appNotResponding;
对于 provider, 在其进程启动时 publish 过程可能会出现 ANR,则会直接杀进程以及清理相应
信息,而不会弹出 ANR 对话框;
考虑上面的 ANR 定义,让我们来研究一下为什么它会在 Android 应用程序里发生和如何构建最佳应用程序来避免 ANR。
1. Android 应用程序通常是运行在一个单独的线程(例如,main)里。这意味着你的应用程序所做事情如果在主线程里占用了台词的时间的话,就会引发 ANR 对话框,因为你的应用程序并没有给自己机会来处理输入事件或者 Intent 广播。
2. 运行在主线程里的任何方法都尽可能少做事情。特别是,Activity 应该在它的关键生命周期方法(如 onCreate() 和 onResume())里尽可能少的去做创建操作。潜在的耗时操作,如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里来完成。然而,不是说你的主线程阻塞在哪里等待子线程的完成,也不是调用 Thread.wait() 或者 Thread.sleep()。代替的方法是,主线程应该为子线程提供一个 Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于 5s 输入事件的超时引发的 ANR 对话框。这种做法应该在其它显示 UI 的线程里效仿,因为它们都受到相同的超时影响。
3. IntentReceiver 执行时间的特殊限制意味着它应该做:在后台里做小的、琐碎的工作如保存设定或者注册一个 Notification。和在主线程里调用的其它方法一样,应用程序应该避免在 BroadcastReceiver 里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver 的生命周期短),代替的是,如果响应 Intent 广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。同时,也应该避免在 IntentReceiver 里启动一个 Activity ,因为它会创建一个新的画面,并从当前用户正在运行的程序抢夺焦点。如果你的应用程序在响应 Intent 广播是需要向用户展示什么,你应该使用 Notification Manager 来实现。
4. 增强响应灵命性
一般来说,在应用程序里,100--200ms 是用户能感知阻滞的时间阈值。因此,这里有一些额外的技巧来避免 ANR,并有助于让你的应用程序看起来有响应性。
如果你的应用程序为响应用户输入正在后台工作的话,可以显示工作的进度(ProgressBar 和 ProgressDialog 对这种情况来说很有用)。
5. 如果你的应用程序有一个耗时的初始化过程的话,可以考虑显示一个 SplashScreen(闪屏页)或者快速显示主画面并异步来填充这些信息。在这两种情况下,你都应该显示正在进行的进度,以免用户认为应用程序被冻结了。
总之,主线程尽量只做 UI 相关的操作,避免耗时操作,比如过度复杂的 UI 绘制,网络操作,文件I/O 操作;避免主线程跟工作线程发送锁的竞争,减少系统耗时 binder 的调用,谨慎使用 sharePreference,注意主线程执行 provider query 操作。尽可能减少主线程的负载,让其空闲待命,以便随时响应用户的操作。
1. 前台 ANR 发生后,系统会马上去抓取现场的信息,用于调试分析,收集的信息如下:
将 am_anr 信息输出到 EventLog,也就是说 ANR 触发的时间点最接近的就是 EventLog 中输出 am_anr 信息。
收集以下重要进程的各个线程调用栈 trace 信息,保存在 data/anr/traces.txt 文件
a. 当前发生 ANR 的进程,system_server 进程以及所有 persistent 进程
b. audioserver, cameraserver, mediaserver, surfaceflinger 等重要的 native 进程
c. CPU 使用率排名前 5 的进程
将发生 ANR 的 reason 以及 CPU 使用情况信息输出到 main log
将 traces 文件和 CPU 使用情况信息保存到 dropbox,即 data/system/dropbox 目录
对用户可感知的进程则弹出 ANR 对话框告知用户,对用户不可感知的进程发生 ANR 则直接杀掉
2. 分析步骤
定位发生 ANR 的时间点
查看 trace 信息
分析是否有耗时的 message, binder 调用,锁的竞争,CPU 资源的抢占
结合具体的业务场景的上下文来分析
解决 ANR 问题
1. FileObserver: 可以监控某个目录/文件状态是否发生改变,或者有没有在某个目录下创建/删除文件,再或者往某个文件里添加内容。自定义 class extends FileObserver , 然后去监听 /data/anr/ 目录,如果该目录发生了变化,说明肯定是有 ANR 问题产生了。
Android 系统再此基础上封装了一个 FileObserver 类来方便使用 Inotify 机制。FileObserver 是一个抽象类,需要定义子类实现该类的 onEvent 抽象方法,当被监控的文件或者目录发生变更事件时,将回调 FileObserver 的 onEvent() 函数来处理文件或目录的变更事件。
2. WatchDog : extends Thread, 监控系统有没有死锁的情况