ANR
ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR
在 Android 里,应用程序的响应性是由 Activity Manager 和 WindowManager 系统服务监视的。当它监测到以下情况中的一个时,Android 就会针对特定的应用程序显示 ANR,在以下场景会显示ANR:
Service Timeout
超过一定时间没有执行完相应操作来触发移除延时消息,则会触发anr。
BroadcastQueue Timeout
有序广播的总执行时间超过 2* receiver个数 * timeout时长,则会触发anr。
有序广播的某一个receiver执行过程超过 timeout时长,则会触发anr。
ContentProvider Timeout
超过 timeout时长,则会触发anr。
InputDispatching Timeout
超过 timeout时长,则会触发anr。
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。
另外:
对于Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding。
对于provider,在其进程启动时publish过程可能会出现ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR的对话框。
如何避免 ANR?
考虑上面的 ANR 定义,让我们来研究一下为什么它会在 Android 应用程序里发生和如何最佳 构建应用程序来避免ANR。
Android 应用程序通常是运行在一个单独的线程(例如,main)里。这意味着你的应用程序所做的事情如果在主线程里占用了太长的时间的话,就会引发 ANR 对话框,因为你的应用程序并没有给自己机会来处理输入事件或者 Intent 广播。因此,运行在主线程里的任何方法都尽可能少做事情。特别是,Activity 应该在它的关键生命周期方法(如 onCreate()和onResume())里尽可能少的去做创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据 库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是 Thread.sleep()。替代的方法是,主线程应该为子线程提供一个 Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将 能保证你的主线程保持对输入的响应性并能避免由于 5 秒输入事件的超时引发的 ANR 对话框。这种做法应该在其它显示 UI 的线程里效仿,因为它们都受相同的超 时影
响。
IntentReceiver 执行时间的特殊限制意味着它应该做:在后台里做小的、琐碎的工作如保存设定或者注册一个 Notifification。和在主线 程里调用的其它方法一样,应用程序应该避免在 BroadcastReceiver 里做耗时的操作或计算。但不再是在子线程里做这些任务(因为BroadcastReceiver 的生命周期短),替代的是,如果响应 Intent 广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。顺便提及一句,你也应该避免在IntentReceiver 里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent 广播时需要向用户展示什么,你应该使用 Notifification Manager 来实现。增强响应灵敏性
一般来说,在应用程序里,100 到 200ms 是用户能感知阻滞的时间阈值。因此,这里有一些额外的技巧来避免 ANR,并有助于让你的应用程序看起来有响应性。如果你的应用程序为响应用户输入正在后台工作的话,可以显示工作的进度(ProgressBar和 ProgressDialog 对这种情况来说很有用)。特别是游戏,在子线程里做移动的计算。如果你的应用程序有一个耗时的初始化过程的话,考虑可以显示一个 SplashScreen 或者快 速显示主画面并异步来填充这些信息。在这两种情况下,你都应该显示正在进行的进度,以 免用户认为应用程序被冻结了。
总结
主线程尽量只做UI相关的操作,避免耗时操作,比如过度复杂的UI绘制,网络操作,文件IO操作。
避免主线程跟工作线程发生锁的竞争,减少系统耗时binder的调用,谨慎使用sharePreference,注意主线程。执行provider query操作。
避免程序逻辑上的死循环。
前台与后台ANR(根据表现形式)
前台ANR:用户能感知,比如拥有前台可见的activity的进程,或者拥有前台通知的fg-service的进程,此时发生ANR对用户体验影响比较大,需要弹框让用户决定是否退出还是等待
后台ANR:,只抓取发生无响应进程的trace,也不会收集CPU信息,并且会在后台直接杀掉该无响应的进程,不会弹框提示用户
所以我们需要做好2种ANR的优化,避免后台ANR的发生,优化输出显示信息避免让用户感知前台ANR。
ANR分析
1. ANR发生后,系统会马上去抓取现场的信息,用于调试分析,收集的信息如下:
将am_anr信息输出到EventLog,也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息。
收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件。其中信息包括
(1)当前发生ANR的进程,system_server进程以及所有persistent进程。
(2)audioserver, cameraserver, mediaserver, surfaceflflinger等重要的native进程。
(3)CPU使用率排名前5的进程。
并将发生ANR的reason以及CPU使用情况信息输出到main log。
将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录。
对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉。
2. 分析步骤
1. 定位发生ANR时间点。
2. 查看trace信息。
3. 分析是否有耗时的message,binder调用,锁的竞争,CPU资源的抢占。
4. 结合具体的业务场景的上下文来分析。
网友评论