Android项目的ANR问题经常让开发者困扰。究其原因,有些ANR问题很难定位。
若ANR和普通crash一样,打出耗时操作的调用栈,那么想办法修复就好了。
最近项目中的ANR问题较为严重,相信每个快速膨胀的app都会经历这个时刻,总结一些经验供大家参考。
ANR原因
ANR是耗时操作造成。
若有A/B/C事件串行执行于UI线程,当C事件发生ANR时,说明A->B->C太耗时了,并不能确定耗时操作在哪个具体事件上。
ANR基本定位
ANR的有用信息,我们需要在trace文件中获取。
trace文件路径:dataanr races_packageName.txt
在trace文件中,我们能够看到如下信息
DALVIK THREAD(14)表明trace文件中收集了14个线程的信息与调用栈。
下面的"main"指的是主线程,我们分析"main"所在的片段即可定位ANR。
图中所示,调用栈指向了com.puff.test.MainActivity.onResume方法,这就是ANR发生的原因,调用到onResume方法时,超时了!
ANR定位不准
当跟随调用栈查看onResume方法时,我们发现唯一的操作便是耗时操作
onResume阻塞了2s,造成了ANR。但是Activity的超时需要5s未响应,看来ANR的原因不仅仅是onResume造成的。
我们继续跟踪onResume之前调用的onCreate与onStart
所以根据trace文件定位ANR未必能准确,trace只给出了造成ANR的直接原因,间接原因仍然需要分析。
ANR辅助定位——BlockCanary
通过trace文件能够确定发生ANR的位置,但只是压死骆驼的最后一根稻草。
而BlockCanary工具却能统计到过程中所有耗时操作(耗时阈值由自己设置)。
原理比较简单,通过替换Looper的Printer达到计算msg处理的耗时。
同时也提供了一些很有帮助的参数,譬如总耗时与线程耗时,可用内存等。
具体内容可详见 BlockCanary
ANR辅助定位——埋点
通过trace文件与BlockCanary工具基本上可以定位耗时操作了,不过仍然有些情况会让人困扰。
若query(*,*)是耗时操作,在onStart中调用了多次,造成了onResume产生ANR,那么虽然能定位query是耗时操作,却不能确定是哪个操作真正耗时。
方法内多次调用同一耗时操作,那么就果断埋点吧!
通过埋点统计操作用时,即可更准确定位耗时操作。
多次采样确定ANR耗时操作
ANR不好复现,耗时操作的耗时也不稳定,通过多次采样来确定耗时操作似乎已经是通用方案了。
若是层级较深或复杂操作才能触发耗时操作的,推荐使用iTestin工具进行自动化测试。
iTestin工具的功能非常丰富,常用的操作都可以通过iTestin来模拟进行,更多的内容就阅读文档吧http://i.testin.cn/。
若是比较简单的操作,可以直接通过adb的shell命令进行自动化测试了。
譬如上例中onResume经常发生ANR,那么多次启动并关闭就可以多次采样了。
adb shell am start com.puff.test/com.puff.test.MainActivity
adb shell am kill com.puff.test
便可执行启动与关闭页面,我们再循环执行一定次数就可以采样了。