阅读 164

如何优化卡顿

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

什么是卡顿

动画卡顿会导致动画画面跳跃打破这种连接感,任何时候屏幕上出现晚于预计的帧都属于卡顿

image.png 卡顿的出现是由于渲染循环(render loop)没有按时完成一帧

渲染循环(render loop)

渲染循环是一个连续有的过程,通过触碰时间传递给app,然后转化到用户界面,向操作系统传送,最终呈现给用户

image.png

帧数

image.png

  • vsync 表示新帧必须准备就绪的时间
  • The Render Loop is timed to VSYNCs,渲染循环跟vsync时间一致,它必须要始终命中检查点来让每帧都做好准备

image.png

  1. 事件被处理,造成用户界面变化,这项工作必须在下一个VSYNC之前完成,这样就能开始下个阶段
  2. 渲染服务(render server),独立进程中进行,这个阶段才正在开始渲染,这项工作也必须在下一个VSYNC之前完成,这样一帧就能显示出来了
  3. 在显示之前对这一整进行双帧处理,称之为双缓冲,还有另外一种模式,为了避免卡顿,系统可能切换到三缓冲,为render server 提供额外的帧持续时间

双缓冲

image.png

三缓冲

The Render Loop.png

整个渲染循环分为五个阶段

  1. 事件阶段,在这个阶段app处理触碰事件决定用户界面是否需要变化
  2. 提价阶段,app会更新永华界面向渲染服务(render server)提交渲染命令
  3. 下个VSYNC中, render server处理命令,渲染准备阶段(render prepare) 为GPU上绘制做好准备
  4. 渲染执行(render execute)GPU将用户的最后图像绘制出来
  5. 在下一个VSYNC这一帧呈现给用户

image.png

image.png

Commit phase.png

image.png

image.png

image.png

image.png

APP中几种可能卡顿类型

主要有两种:

  1. 提交卡顿,发生在app的处理中,app花费过长时间来出路或提交事件,导致在下一个VSYNC渲染服务没有东西处理,所以必须等待下一个VSYNC开始渲染,
  2. 渲染卡顿,发生在渲染服务器中,渲染服务器无法按时或者执行图层树时出现

image.png

image.png

image.png

如何测量和量化卡顿

  1. iOS设备并不总是更新屏幕,如果没有提交到渲染服务器上,新的一帧就不会被提交
  2. 通过测试和设备来比较卡顿时间就更困难
  • 卡顿时间比(measuring hitch time): 就是一个区间的总卡顿时间除以它的持续时间,它是有每秒中的卡顿毫秒时间来测定的,所以它代表着设备在每秒出现卡顿的毫秒数

image.png

image.png

  • 找出并修复渲染循环(render loop)提交阶段(commit phase)中的卡顿

什么是 commint phase

image.png

  • 通过调用drawrect 或者 layoutsubviews 来进行相对应的更新

image.png

image.png

image.png

  • 筹备阶段,还没有解码的图像,会在这一步进行解码,对于较大的图像会花很长的时间,若某个图像的颜色格式图像处理器无法直接使用,也会在这一步进行转换,这就要求对某个图像进行复制,而不是将光标直接发送到原图上,这样耗时更长,使用的内存更多。

image.png

  • 视图层次结构将被递归打包并发送到渲染服务器,深层视图层次结构打包时间要长一些

image.png

使用Instruments 寻找卡顿

Screen Shot 2021-01-31 at 18.12.09.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

Screen Shot 2021-01-31 at 18.23.28.png

image.png

避免体提交卡顿的建议

保持视图的轻量

  • 尽可能在CALayer上CPU加速的可用属性,并避免使用中央处理器自定义绘图
  • 若一定要用,切记要测量其性能,因为系统要做额外的工作,从而需要处理下一事务时,使用更长的耗时并占用更多的内存,所以避免出现空的drewRect(react:)实现
  • 尽量复用视图,避免使用代价过高的视图层级结构操作,如添加和移除
  • 如果把视图从某一动画移除尽量使用隐藏属性

image.png

减少代价过高且重复的布局

  • 需要更新布局是尽量使用 setNeedsLayout(),layoutIfNeeded 会消耗单前事务的生命周期,也会造成卡顿,大多可以等到下一次循环执行的时候再更新你的布局
  • 试着使用最少的 约束,来避免解决问题时增加难度
  • 视图应该只能自己或者自己的子视图无效,而不能使其同级视图或者父视图无效,不然的话,视图的布局就会再一次陷入递归性无效

image.png

查明并消除渲染卡顿

渲染卡顿的两个阶段是什么

image.png

  • 渲染准备,图层树被编译成一系列简单的操作供给GPU执行,在几个帧上发生的动画也在此进行处理
  • 渲染执行, GPU将app图层绘制成最终的图像准备显示
  • 这两个阶段都可能延时帧的交付时间

渲染过程

  • 图层树开始,渲染服务器会逐层编译一系列绘图命令
  • 使图形处理器能从后向前绘制用户界面,从根节点开始,渲染服务器从同级到同级,从父级到子级,直到涵盖层级中的每个图层

image.png

执行管线

  • GPU不知用什么形状来绘制阴影
  • 所以必须切换不同的纹理来确定阴影的形状,称之为“离屏渲染”
  • 在最终textture(纹理)以为绘制
  • 将阴影形状隔离在离屏纹理内
  • 通过先将图层变成黑色,然后将其模糊
  • 将离屏textture复制到最终的 texttrue中

image.png

image.png

image.png

image.png

offscreeen 离屏通道

  • GPU必须先在其他地方渲染一个图层,然后再将其复制过来,就阴影而言,它必须绘制图层以确定形状,
  • 离屏通道可能会极少成多到时渲染出现卡顿,所以要尽量避免离屏通道
  • 有四种主要的offscreen 可以优化
    • shadowing(阴影)

    • Masking(遮罩)

      • 当图层或图层树需要遮罩时,渲染器需要渲染被遮罩的子树,但它也需要避免覆盖被遮避形状之外的像素,将只会被屏蔽形状内的像素,复制待最终texttrue之前渲染整个子树
    • Rounded reactangles(圆角矩形)

      • 将园化形状neural的像素复制回来
    • Visual effects(视觉效果)

image.png

如何是使用Instruments 和Xcode视图调试器

Screen Shot 2021-01-31 at 19.35.48.png

image.png

image.png

优化图层树的建议并繁殖卡顿干扰用户体验

  • 始终使用提供的API,在设置阴影时确保设置了shadowPath,以减少大量的离屏通道
  • 圆角使用 cirenerRadius 和 cornerCurve, 避免用蒙版或角内容来构成圆角矩形形状
  • 优化整个APP的 masking across ,使用 masksToBounds 遮蔽为矩形,圆角矩形或者椭圆形
  • 如果子树中的内容不会超出边界,则完全禁用masksToBounds

image.png

maskToBounds.png

相关WWDC视频:

《Eliminate Animation Hitches with XCTest》

《What's New in MetricKit.》

《Hight Performance auto layout》

《Image and Graphics Best Practicers》

文章分类
iOS
文章标签