ANR概念
anr是指应用程序无响应,Android系统对于一些事件需要在一定时间范围内完成,如果超过预定时间未能得到有效响应或者响应时间过长,都会造成anr。通常发生anr时,系统会弹出一个提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。
在Android里,应用程序的响应性是由ams哥wms监视的。当它检测到以下场景中的一个时,Android就会针对特定的应用程序显示ANR(一个弹窗):
场景
-
Service Timeout
对于前台服务,Timeout为20s。
对于后台服务,Timeout为200s。
logcat日志关键字:Timeout executing service -
BroadcastQueue TImeout
对于前台广播,Timeout为10s。
对于后台广播,Timeout为60s。
logcat日志关键字:Timeout of broadcast BroadcastRecord -
ContentProvider Timeout
Timeout为10s。
logcat日志关键字:timeout publishing content providers -
InputDispatching Timeout(主要anr类型)
输入事件分发Timeout为5s,包括按键和触摸事件。
注意事项:Input的超时机制与其他不同,对于input来说即便某次事件执行时间超过Timeout时长,只要用户后续没有再生成输入事件,则不会触发anr。
logcat日志关键字:Input event dispatching timed out
超时检测机制
- service超时检测机制:超过一定时间没有执行完相应操作,则会触发anr;
- BroadcastReceiver超时检测机制:有序广播的总执行时间超过2*receiver个数*timeout时长,则会触发anr;有序广播的某一个receiver执行过程超过timeout时长,则会触发anr
- 对于service,broadcast,input发生anr之后,最终都会调用AMS.appNotResponding;对于provider,在其进程启动时可能会触发anr,则会直接杀进程以及清理相应信息,不会弹出anr弹窗。
如何避免ANR发生
- 主线程尽量只做UI相关的操作,避免耗时操作,比如过度复杂的UI绘制,网络操作,文件IO操作
- 避免主线程跟工作线程发生锁的竞争,减少系统耗时binder的调用,谨慎使用sharePreference。
- 总之,尽量减少主线程的负载,让其空闲待命,以便随时可以响应用户的操作
- Android应用程序通常运行在一个单独的线程(例如:main)里。这意味着应用程序所做的事情如果在主线程里占用了太长的时间,就会引发anr弹窗,因为应用程序并没有给自己机会来处理输入事件或者intent广播。
- 因此,运行在主线程里的任何方法尽可能少做事情。特别是activity应该在它的关键生命周期方法(如onCreate或onResume)里尽可能少的去做创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里来完成。然而,不是说主线程阻塞在那里等待子线程完成——也不是调用Thread.wait或Thread.sleep。替代的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计应用程序,将能保证主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的anr对话框。这种做法应该在其它显示UI的线程里效仿,因为它们都受相同的超时影响。
- IntentReceiver执行时间的特殊限制意味着它应该做:在后台做小的、琐碎的工作如保存设定或者注册一个Notification。和在主线程调用其他方法一样,应用程序应该避免在BroadcastReceiver里做耗时操作或计算。但不再是在子线程里做这些任务(因为BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时动作,应用程序应该启动一个Service。我们也应该避免在IntentReceiver里启动一个activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点,如果应用程序在响应intent广播时需要向用户展示什么,应该使用NotificationManager来实现。
- 一般来说,在应用程序里,100到200ms是用户能够感知阻滞的时间阈值。因此,这里有一些额外的技巧来避免anr,并有助于增强应用程序的响应灵敏性。如果应用程序为响应用户输入正在后台工作,可以显示工作的进度,如果应用程序有一个耗时的初始化过程,考虑可以显示一个splashscreen或者快速显示主画面并异步来填充这些信息。在这两种情况下,都应该显示正在进行的进度,以免用户认为应用程序被冻结了。
前台与后台ANR
- 前台anr:用户能感知,比如拥有前台可见的activity进程,或者拥有前台通知的fg-service的进程,此时发生anr对用户体验影响比较大,需要弹框让用户决定是否退出或等待。
- 后台anr:只抓取发生无响应进程的trace,也不会收集cpu信息,并且会在后台直接杀掉该无响应的进程,不会弹框提示用户。
ANR分析
- 前台anr发生后,系统会马上抓取现场的信息,用于调试分析,收集的信息如下:
-
将am_anr信息输出到eventlog,也就是说anr触发的时间点最接近的就是eventlog中输出的am_anr信息
-
收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件
-
将发生anr的reason以及cpu使用情况信息输出到main log
-
将traces文件和cpu使用情况信息保存到dropbox,即data/system/dropbox目录
-
对用户可感知的进程则弹出anr弹窗告知用户,不可感知的进程发生anr直接杀掉
2.分析步骤
- 定位发生anr时间点
- 查看trace信息
- 分析是否有耗时的message,binder调用,锁竞争,cpu资源的抢占
- 结合具体的业务场景的上下文来分析
trace.txt参数解读
**main**:main标识是主线程,如果是线程,那么命名成“Thread-X”的格式,x表示线程id,逐步递增。
**prio**:线程优先级,默认是5
**tid**:tid不是线程的id,是线程唯一标识ID
**group**:是线程组名称
**sCount**:该线程被挂起的次数
**dsCount**:是线程被调试器挂起的次数
**obj**:对象地址
**self**:该线程Native的地址
**sysTid**:是线程号(主线程的线程号和进程号相同)
**nice**:是线程的调度优先级,值越小优先级越高
**sched**:分别标志了线程的调度策略和优先级
**cgrp**:调度归属组
**handle**:线程处理函数的地址。
**state**:是调度状态
**schedstat**:从 `/proc/[pid]/task/[tid]/schedstat`读出,三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度,不支持这项信息的三个值都是0;
**utm**:是线程用户态下使用的时间值(单位是jiffies,默认等于10ms)
**stm**:是内核态下的调度时间值(单位是jiffies,默认等于10ms)
**core**:是最后执行这个线程的cpu核的序号。
**utm+stm==schedstat**
参考资料
https://blog.csdn.net/denglusha737/article/details/86706909
https://blog.csdn.net/abc6368765/article/details/127220609