只有当应用程序的UI线程响应超时时才会引起ANR,超时产生原因一般有两种:
--当前的事件没有机会得到处理,例如UI线程正在响应另一个事件,当前事件由于某种原因被阻塞了。
--当前的事件正在处理,但是由于耗时太长没能及时完成。
根据ANR产生的原因不同,超时时间也不尽相同,从本质上讲,产生ANR的原因有三种,大致可以对应到Android中的四大组件中的三个:
--KeyDispatchTimeout 最常见的一种类型,原因是View的按键事件或触摸事件在特定的时间(5秒)内无法得到响应。
--BroadcastTimeout的原因是BroadcastReceiver的onReceive()函数运行在主线程中,在特定的时间(10秒)内无法完成处理。
--ServiceTimeout是比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20秒)内无法完成处理。
当发生ANR时,在手机内部存储的/data/anr/traces.txt文件会被生成,还有logcat日志也有相应的信息生成。
logcat日志中的关键词是wrote stack traces to '/data/anr/traces.txt',还包含如下内容:
--导致ANR的类名及所在包名
--发生ANR的进程名及ID
--ANR产生的原因Reason
--系统中活跃进程的CPU占用率
同时如果由于ANR导致应用发生崩溃,那么除了打印出上面的信息之外,还调用CrashAnrDetector打印出其他辅助信息。
Traces.txt文件中可以看到如下有助于定位问题的信息:
--发生ANR的进程名称、ID,以及时间
--手机的CPU架构
--主线程基本信息(名称、优先级、锁ID、状态)
--主线程的详细信息
--线程组名称
--线程的Java对象地址
--线程本身的Native对象地址
--线程的调度信息
--线程的上下文信息
--线程的堆栈信息
为了避免ANR,我们也可以借助于一些工具来进行检测:
--StrictMode是Android SDK提供的一个用来检测代码中是否存在违规操作的工具类,StrictMode主要检测两大类问题。其中线程策略ThreadPolicy包括
1)detectCustomSlowCalls(检测自定义耗时操作)
2)detectDiskReads(检测是否存在磁盘读取操作)
3)detectDiskWrites(检测是否存在磁盘写入操作)
4)detectNetwork(检测是否存在网络操作)
虚拟机策略V名Policy包括
1)detectActivityLeaks(检测是否存在Activity泄漏)
2)detectLeakedClosableObjects(检测是否存在未关闭Closable对象泄漏)
3)detectLeakedSqlLiteObjects
4)setClassInstanceLimit(检测类实例个数是否超过限制)
StrictMode的使用很简单,我们只需要在应用初始化的地方,例如Application或MainActivity的onCreate方法执行StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());其中的penalty表示在Logcat中打印日志,detectAll方法表示启动所有的检测策略。
--BlockCanary是一个非侵入式的性能检测函数库,它的用法和LeakCanary类似,只不过后者监控应用的内存泄漏,而BlockCanary主要用来监控应用主线程的卡顿。它的基本原理是利用主线程的消息队列处理机制,通过对比消息分发开始和结束的时间点来判断是否超过设定的时间。它的集成很简单,首先在build.gradle中添加在线依赖,在Application类中就可以创建BlockCanaryContext及其子类设置各种检测条件,并调用BlockCanary的install方法关联BlockCanaryContext并开始工作。