当前位置: 首页 > article >正文

卡顿优化小结

卡顿的本质

卡顿的本质是因为一次垂直同步信号来的时候,当前帧要显示的图像数据还没准备好,只能等待16ms下一次垂直同步信号来时才能更新画面,在这段时间里显示器只能一直停留在上一帧的画面,如果跳过的帧数过多,就会看到上一帧的画面一直没有变化,给人的感觉就是卡顿;

垂直同步信号:是为了解决显示器刷新率和CPU/GPU生产图形数据速度不匹配问题;当CPU/GPU制造图形数据的速度大于显示器刷新速度时,生产的很多帧数据就浪费了,浪费了cpu/GPU的性能;而当CPU/GPU制造帧数据的速度小于显示器刷新速度时,就会导致跳帧,也就是会感觉画面更新很慢,给人卡顿的感觉;

有了垂直同步信号后,当屏幕显示完一帧画面后会发送一个垂直同步信号,cpu、gpu在接收到信号后才开始生产帧数据,这样就不会浪费cpu/GPU性能

GPU的ALU单元比CPU多很多,GPU更擅长计算;而cpu更擅长逻辑运算

卡顿优化

造成卡顿的原因主要有这三个方面:

  • 第一,在主线程上做了一些耗时操作,我们需要将这些耗时操作转移到线程池中执行

  • 第二,短时间内创建大量对象造成频繁GC,比如在自定义View的onDraw方法中、列表滚动事件中、创建了大量对象就会引起GC频繁;解决办法就是通过享元模式复用已经创建过的对象,避免对象大量重复创建

  • 第三,布局设计不合理,导致过度绘制,层级过多,从而引起绘制需要的时间更长;解决办法是通过include、merge、viewStub、ConstraintLayout这些标签对布局进行优化,减少布局的层级,减少过度绘制的现象

卡顿监测

  • 对于布局方面的检测,我们可以通过Android Studio自带的Layout Inspector查看界面中的布局情况;在开发者模式中打开过度绘制开关,可以查看应用过度绘制情况

  • 通过编舞者类不断请求发起垂直同步信号请求,每次回调时计算跳过的帧数,超过一定的阈值,就认为卡顿发生了;编舞者类也会打印出跳帧的系统log

  • 在一些可能比较耗时的代码前后预先插入统计耗时的代码;也可以调用Trace.beginSection和endSection代码,然后通过系统工具systemtrace分析该段代码运行情况

  • 如果需要定位到具体比较耗时的代码,可以通过给主线程Looper设置一个自定义打印log类,Looper会在事件分发前后使用该Log打印类输出log;我们可以在事件分发之前开始定时收集主线程的调用栈信息,在事件分发结束后判断耗时情况,如果跳过一定的帧数,则判定卡顿发生,并且打印出这段时间内主线程的调用栈情况,从而定位到是主线程哪里耗时比较多,第三方监控卡顿框架BlockCanary就是通过这种方式实现卡顿监控的

UI渲染机制

  • 当我们在Activity的onCreate方法中设置一个布局之后,会先创建一个根布局DecorView,然后通过LayoutInflater将我们设置的xml布局文件解析成View对象并添加到DecorView中

  • 接着在onResume生命周期过后会回调makeVisible方法;接着依次调用WindowManagerImpl\WindowManagerGlobal的addView方法,为DecorView生成一个ViewRootImpl对象用于管理DevorView以及和WMS通信;

  • 接着DecorView会调用setView方法,在里面会调用requestLayout方法请求开始绘制,并将窗体相关信息添加到WMS中

  • 在requestLayout方法中不会立刻执行View的三大绘制流程方法,而是先通过消息队列发送一个同步屏障消息,然后通过编舞者类发起一次垂直同步信息请求,当垂直同步信息来的时候,ViewRootImpl就开始绘制并调用View的onMeasure\onLayout\onDraw方法,在调用绘制方法draw之前,会通过Surface创建一个画布Canvas传递到各个View的onDraw方法中,各个View就在这个画布上进行绘制图像;绘制完成之后,会将这个带有绘制数据的Surface发送给SurfaceFlinger,SurfaceFlinger将应用绘制数据和系统UI数据整合之后交给GPU进行栅格化渲染,最终形成可供显示器显示的一帧数据存到帧缓冲区,等待一次垂直同步信号后显示到屏幕

  • 之后的每次View刷新,都会最终调用到ViewRootImpl的requestLayout方法,等待一次垂直同步信号后执行绘制流程

编舞者类Choreographer

编舞者类主要是用于控制绘制节奏,通过请求垂直同步信号,接收垂直同步信号来通知应用层绘制的时机,主要流程如下:

  • 编舞者类在每一个线程都有一个唯一的单例对象,它是存在ThreadLocal中的

  • 当调用它的postCalllback或者postFrameCallback方法时,会将回调方法保存到队列中,这两个方法的区别是postFrameCallback请求的是动画类型的垂直同步信号,而postCallback支持传入指定类型的垂直同步信号;垂直同步信号类型一共有五种:分别用于 输入、动画、插入动画、绘制、提交;不同类型只是它们的callback调用顺序不一样,input类型的callback最先调用

  • 接着会判断要不要延迟发起请求、当前线程是否跟Looper线程是否一致来决定要不要立刻发起请求;发起请求是通过调用native方法实现的,主要也是通过向SurfaceFlinger注册监听垂直同步信号

  • 当接收到垂直同步信号时,会调用编舞者类的回调方法,编舞者类接着会通过Handler发送异步消息去调用doFrame方法

  • 在doFrame方法中会判断有没有跳帧,因为消息队列中可能会有其他耗时任务在执行,导致doFrame方法执行延后,如果跳帧了就输出log、接着就会依次调用那五种类型的Callback回调函数

  • 然后就可以在接收到垂直同步信号后进行UI绘制、动画等操作

同步屏障

Handler中有三种类型的消息,同步消息、同步屏障和异步消息;Message里有个属性标记是否异步,默认是同步消息;而同步屏障消息是一个target等于null的Message对象,这个target也就是Handler;当消息队列遍历到这个同步屏障消息时,会开启一个while循环,优先遍历处理之后的异步消息;


http://www.kler.cn/a/7218.html

相关文章:

  • 【STM32】USB 简要驱动软件架构图
  • pytorch tensor在CPU和GPU之间转换,numpy之间的转换
  • 【网络安全面经】OSI七层模型每层都有什么协议
  • RedHat7—Linux中kickstart自动安装脚本制作
  • Go语言24小时极速学习教程(四)MySQL数据库的增删改查
  • leetcode面试 150题之 三数之和 复刷日记
  • 计算机视觉的应用1-OCR分栏识别:两栏识别三栏识别都可以,本地部署完美拼接
  • 从零开始实现一个C++高性能服务器框架----Socket模块
  • 【分享】免梯子的GPT,玩 ChatGPT 的正确姿势
  • 《底层逻辑》读书笔记
  • python的元类
  • IDEA中查看源码点击Download Sources时出现Cannot download sources的问题复现及解决
  • C++ Primer第五版_第十章习题答案(31~40)
  • leetcode53:最大子数组和
  • Kotlin 基础语法
  • 【数据库运维】mysql备份恢复练习
  • 【nnunet】个人数据训练心得
  • STL容器篇之stack和queue
  • 数学建模在大数据与数据挖掘、复杂网络与系统建模方面的应用
  • 如何让 a == 1 a == 2 a == 3 成立
  • iptables防火墙详解
  • Linux系统【Centos7】设置防火墙教程
  • record 替代 lombok, 我觉得不行
  • 58-Map和Set练习-LeetCode692前k个高频单词
  • AIGC之Stable Diffusion 提示词学徒库
  • 「ML 实践篇」回归系统:房价中位数预测