常见anr
input,点击事件:5秒; contentprovider:10秒; Broadcast:前台10秒,后台60秒; 服务 service:前台20秒,后台200秒
原理
埋炸弹和拆炸弹
当启动service的时候,会调用
scheduleCreateService方法创建service
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
在创建过程中,利用handler发送延时消息,预埋炸弹
mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
如果在延时时间内完成对应的操作,则handler会移除掉刚才的延时消息,拆除炸弹
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
这种利用handler的延迟消息,埋炸弹和拆炸弹的设计思想,可以在日常开发中使用和借鉴。如watchdog就是使用该原理实现的
如果在规定的时间内炸弹没有被拆除,则会执行系统的appNotResponding方法,搜集当前堆栈信息,打印到控制台和写入trace文件中
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation){
}
除了input事件外,其他的anr类型都是采用的这种装炸弹和拆炸弹的方式,input采用的是接受下一个input事件时再去检查是否anr的方案
核心流转代码--Loop.loop()函数
public static void loop() {
for (;;) {
Message msg = queue.next();
...
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
msg.target.dispatchMessage(msg);
...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
...
}
anr从代码流程和监控上来说主要有两个收口点,分别是Message msg = queue.next()函数执行过长,或者是msg.target.dispatchMessage(msg);具体的消息的执行函数时间过长,所以从监控上只需要监控这两个点就可以。
监控方案
BlockCanary,watchDog 方案
网上很多方案如BlockCanary,watchDog是通过监控 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 来计算是否耗时,从而打印出堆栈来定位卡断的,但是这个时候定位到的卡顿信息并不准确,因为此时函数已经执行完毕,并不能准确反映卡顿现场的具体堆栈信息。在实际操作中需要密集dumo堆栈才能获取准确信息。但是密集dump堆栈带来的卡顿和大hprof文件传输,使得结果适得其反
字节码插桩
利用Gradle Plugin和ASM,在方法的开始和结束前后各加入一个统计耗时的方法,然后定义好阈值,超过阈值则打印出对应的堆栈。这个时候获取到的堆栈是准确的,但是会引起方法数暴增,因此要做好策略和字节码插桩的范围。
线上监控方案
无论是字节码插桩还是BlockCanary或watchDog ,都不宜全量带到线上正式包中。实际操作中可以采用二者相互结合的方式。
如debug和灰度期间可以是用字节码插桩监控耗时和anr的包,同时监控logging.println("<<<<< Finished to " + msg.target + " " + msg.callback)的代码在有开关和策略的配合下带到线上,根据具体问题和需求灵活关闭和开启。做到既可以解决问题,同时又不影响性能
BlockCanary和watchdog的改进
网上的开源库一般具有普遍适用的特点,但是我们要根据我们项目自身的特点和需求进行改造,或者参考其源码和设计进行自己编写。
如BlockCanary,我们可以自定义监控logging.println("<<<<< Finished to " + msg.target + " " + msg.callback),在监测到卡顿时候增加自己业务特殊的逻辑,同时适配开关和动态降级策略。
watchdog基于埋炸弹和拆炸弹思想实现,每5秒计算一下是否卡顿,因此容易造成5秒的误差。我们可以将间隔时间改造成1秒,累计连续监测到5次标记位没被改变,则可以认为是发生了卡顿。这样既减少误差,同时也不会影响性能。
**除以上知识点之外,还有大厂面试讲解和android开发视频可以前往观看:[https://www.bilibili.com/video/BV12b4y1a7Fd/
如果你觉得有所收获,可以点赞收藏关注哦,接下来,我将继续对Android的相关知识进行分析和分享,可以继续关注哦,要是真正想学习android开发这块的,或者在这方面工作的朋友我免费分享给你,当然也希望大家都能多多支持我,你们的点赞就是我的动力,谢谢!
|