Android 性能优化--卡顿,ANR ,方法耗时监测

常见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次标记位没被改变,则可以认为是发生了卡顿。这样既减少误差,同时也不会影响性能。

猜你喜欢

转载自juejin.im/post/7013623727640297485