这是我参与更文挑战的第2天,活动详情查看: 更文挑战
什么是卡顿
动画卡顿会导致动画画面跳跃打破这种连接感,任何时候屏幕上出现晚于预计的帧都属于卡顿
卡顿的出现是由于渲染循环(render loop)没有按时完成一帧
渲染循环(render loop)
渲染循环是一个连续有的过程,通过触碰时间传递给app,然后转化到用户界面,向操作系统传送,最终呈现给用户
帧数
- vsync 表示新帧必须准备就绪的时间
- The Render Loop is timed to VSYNCs,渲染循环跟vsync时间一致,它必须要始终命中检查点来让每帧都做好准备
- 事件被处理,造成用户界面变化,这项工作必须在下一个VSYNC之前完成,这样就能开始下个阶段
- 渲染服务(render server),独立进程中进行,这个阶段才正在开始渲染,这项工作也必须在下一个VSYNC之前完成,这样一帧就能显示出来了
- 在显示之前对这一整进行双帧处理,称之为双缓冲,还有另外一种模式,为了避免卡顿,系统可能切换到三缓冲,为render server 提供额外的帧持续时间
双缓冲
三缓冲
整个渲染循环分为五个阶段
- 事件阶段,在这个阶段app处理触碰事件决定用户界面是否需要变化
- 提价阶段,app会更新永华界面向渲染服务(render server)提交渲染命令
- 下个VSYNC中, render server处理命令,渲染准备阶段(render prepare) 为GPU上绘制做好准备
- 渲染执行(render execute)GPU将用户的最后图像绘制出来
- 在下一个VSYNC这一帧呈现给用户
APP中几种可能卡顿类型
主要有两种:
- 提交卡顿,发生在app的处理中,app花费过长时间来出路或提交事件,导致在下一个VSYNC渲染服务没有东西处理,所以必须等待下一个VSYNC开始渲染,
- 渲染卡顿,发生在渲染服务器中,渲染服务器无法按时或者执行图层树时出现
如何测量和量化卡顿
- iOS设备并不总是更新屏幕,如果没有提交到渲染服务器上,新的一帧就不会被提交
- 通过测试和设备来比较卡顿时间就更困难
- 卡顿时间比(measuring hitch time): 就是一个区间的总卡顿时间除以它的持续时间,它是有每秒中的卡顿毫秒时间来测定的,所以它代表着设备在每秒出现卡顿的毫秒数
- 找出并修复渲染循环(render loop)提交阶段(commit phase)中的卡顿
什么是 commint phase
- 通过调用drawrect 或者 layoutsubviews 来进行相对应的更新
- 筹备阶段,还没有解码的图像,会在这一步进行解码,对于较大的图像会花很长的时间,若某个图像的颜色格式图像处理器无法直接使用,也会在这一步进行转换,这就要求对某个图像进行复制,而不是将光标直接发送到原图上,这样耗时更长,使用的内存更多。
- 视图层次结构将被递归打包并发送到渲染服务器,深层视图层次结构打包时间要长一些
使用Instruments 寻找卡顿
避免体提交卡顿的建议
保持视图的轻量
- 尽可能在CALayer上CPU加速的可用属性,并避免使用中央处理器自定义绘图
- 若一定要用,切记要测量其性能,因为系统要做额外的工作,从而需要处理下一事务时,使用更长的耗时并占用更多的内存,所以避免出现空的drewRect(react:)实现
- 尽量复用视图,避免使用代价过高的视图层级结构操作,如添加和移除
- 如果把视图从某一动画移除尽量使用隐藏属性
减少代价过高且重复的布局
- 需要更新布局是尽量使用 setNeedsLayout(),layoutIfNeeded 会消耗单前事务的生命周期,也会造成卡顿,大多可以等到下一次循环执行的时候再更新你的布局
- 试着使用最少的 约束,来避免解决问题时增加难度
- 视图应该只能自己或者自己的子视图无效,而不能使其同级视图或者父视图无效,不然的话,视图的布局就会再一次陷入递归性无效
查明并消除渲染卡顿
渲染卡顿的两个阶段是什么
- 渲染准备,图层树被编译成一系列简单的操作供给GPU执行,在几个帧上发生的动画也在此进行处理
- 渲染执行, GPU将app图层绘制成最终的图像准备显示
- 这两个阶段都可能延时帧的交付时间
渲染过程
- 图层树开始,渲染服务器会逐层编译一系列绘图命令
- 使图形处理器能从后向前绘制用户界面,从根节点开始,渲染服务器从同级到同级,从父级到子级,直到涵盖层级中的每个图层
执行管线
- GPU不知用什么形状来绘制阴影
- 所以必须切换不同的纹理来确定阴影的形状,称之为“离屏渲染”
- 在最终textture(纹理)以为绘制
- 将阴影形状隔离在离屏纹理内
- 通过先将图层变成黑色,然后将其模糊
- 将离屏textture复制到最终的 texttrue中
offscreeen 离屏通道
- GPU必须先在其他地方渲染一个图层,然后再将其复制过来,就阴影而言,它必须绘制图层以确定形状,
- 离屏通道可能会极少成多到时渲染出现卡顿,所以要尽量避免离屏通道
- 有四种主要的offscreen 可以优化
-
shadowing(阴影)
-
Masking(遮罩)
- 当图层或图层树需要遮罩时,渲染器需要渲染被遮罩的子树,但它也需要避免覆盖被遮避形状之外的像素,将只会被屏蔽形状内的像素,复制待最终texttrue之前渲染整个子树
-
Rounded reactangles(圆角矩形)
- 将园化形状neural的像素复制回来
-
Visual effects(视觉效果)
-
如何是使用Instruments 和Xcode视图调试器
优化图层树的建议并繁殖卡顿干扰用户体验
- 始终使用提供的API,在设置阴影时确保设置了shadowPath,以减少大量的离屏通道
- 圆角使用 cirenerRadius 和 cornerCurve, 避免用蒙版或角内容来构成圆角矩形形状
- 优化整个APP的 masking across ,使用 masksToBounds 遮蔽为矩形,圆角矩形或者椭圆形
- 如果子树中的内容不会超出边界,则完全禁用masksToBounds
相关WWDC视频:
《Eliminate Animation Hitches with XCTest》
《What's New in MetricKit.》
《Hight Performance auto layout》
《Image and Graphics Best Practicers》