本文学习来源为 Apple tech-talks,仅自己作为学习笔记.如有误解之处,请不吝指教.
什么是卡顿?
用户使用App时候可能会轻点按钮,滑动等操作,得到反馈或在层级中进行视图转换,这些动画构建了一种,用户和屏幕内容的视觉连接感
但是,动画如果卡顿,会导致动画画面跳跃,打破这种连接感,这会使人感到困惑而非愉悦(强迫症)
任何时候屏幕上出现 晚于预计的帧都属于卡顿
show case
在这里我们看到一个膳食计划app,当用户在屏幕上滑动手指时,滚动视图会随着上移内容作出响应,但在滚动的同时,我们留意到了内容的跳跃.我们来一帧一帧的去看这个
我们就能看到在前三帧里,我们的手指,随着内容移动,但是在下一帧里,内容似乎不动了,这是因为第三帧重复了 停留在显示器上形成了另一帧,最后才是第四帧,它似乎跳到了我们手指的位置
第三帧重复了,第四帧延迟了,用户就看到了卡顿
卡顿产生的原因
卡顿的出现是由于渲染循环 没有按时完成一帧 我们就来看一下渲染循环
什么是渲染循环?(Render Loop)
渲染循环是一个连续性的过程 通过触碰事件 传送给 app 然后转化到用户界面 向操作系统传送 最终呈献给用户 这就是循环 随着设备的刷新率发生
在iPhone和iPad中每秒有60帧,这意味着每 16.67毫秒就可以显示一个新帧, 在iPad Pro上刷新率是每秒 120 帧,这意味着每8.33 毫秒就可以显示一个新帧
在每一帧的初始触发事件的硬件叫做VSYNC,VSYNC表示新帧必须准备就绪的时间 我们在显示轨迹上将它们标记出来 所以很容易看到截止期渲染循环和 VSYNC 的时间一致 它必须要始终命中检查点 来让每帧都做好准备
App渲染的过程
App处理阶段
app中的第一个阶段,操作举动会被处理,造成用户界面的变化,这项工作必须在下一个 VSYNC 之前完成, 这样下一个阶段就能开始了
渲染服务处理阶段
第二阶段在一个叫做渲染服务器的独立进程中进行,这个阶段用户界面才真正被渲染,这项工作也必须在下一个 VSYNC之前完成,这样一帧就能显示出来了
显示阶段
第三阶段, 在显示之前 对这一帧进行双帧处理,我们把这称之为双缓冲 但还有另外一个模式,为了避免卡顿 系统可能会切换到三缓冲,为渲染服务器提供了一个额外的帧持续时间来完成工作,这是备用模式,这里不做过多讲解
整个渲染循环由五个阶段组成 循环从第一个阶段开始 事件阶段 在这个阶段 你的 app 处理触碰事件 决定用户界面是否需要变化 下一个是提交阶段 在提交阶段 你的 app 会更新用户界面 向渲染服务器提交渲染命令 在下一个 VSYNC 中 渲染服务器处理命令 在渲染准备阶段 为在图形处理器上绘制做好准备 在渲染执行阶段 图形处理器将 用户界面的最后图像绘制出来 所以在下一个 VSYNC 这一帧将会呈现给用户
事件阶段 Event
你的 app 处理触碰事件 决定用户界面是否需要变化
提交阶段 commit
在提交阶段 你的 app 会更新用户界面,向渲染服务器提交渲染命令,在下一个 VSYNC 中 渲染服务器处理命令
渲染准备阶段 Render prepare
在渲染准备阶段 为在图形处理器上绘制做好准备
渲染执行阶段 Render execute
渲染执行阶段 图形处理器将 用户界面的最后图像绘制出来
显示阶段 DisPlay
将这一帧将会呈现给用户
要想每一帧都拥有流畅的用户体验 每个阶段都很重要
事件处理阶段
当 app 更新了图层的限制范围 Core Animation 同时会调用 setNeedsLayout 它能够分辨所有图层 必须要以重新计算布局作为回应
系统会合并这些“需要布局”的请求 并在提交阶段按顺序执行 以减少重复工作
提交阶段 commit
如果需要任何布局 一旦事件阶段结束后 提交阶段会自动开始 首先 系统会挑选需要布局的图层 从父级到子级依次布局
布局是常见的性能瓶颈 所以要牢记 你的 app 只有几毫秒 来完成这项工作 有些视图还需要自定义绘图 像标记、图像视图 或者只是任何覆盖 drawRect 的视图 如果这些视图需要视觉更新 他们必须调用 setNeedsDisplay 像布局一样 系统会合并这些请求 完成所有布局后执行这些操作
有了 Core Animation 这些图层就变成了图片 现在所有的图层都已经布局并绘制好了 整个修改好的图层树会被收集 并且发送到渲染服务器进行渲染
渲染准备阶段 Render prepare
这里负责将我们的图层树转换为 真正可显示的图像
在准备阶段 渲染服务器迭代app的图层树,准备一个线性管线 这样图形处理器就能执行命令
从最上面的图层开始 它是从父级到子级 同级到同级间进行的 所以图层是从后到前安排的
渲染执行阶段 Render execute
渲染执行阶段,这个线性管线从图形处理器经过 每个图层组成了最后的纹理 有些图层需要更长的时间来渲染 这也是我们稍后我们需要讨论的,常见性能瓶颈
显示阶段 DisPlay
所以一旦图形处理器执行,并在右侧渲染图像 就准备好展示在下一个 VSYNC 了
每个渲染循环的阶段都具有性能敏感性 并且都有截止期 截止期就是下一个 VSYNC 为了达到目标帧速率 保持低输入延迟 整个过程实际上是在每一帧中并行进行的
这样管线就成了并行的 在系统渲染前一个的同时 我们的 app 可以准备一个新帧 所以每个截止期的错失都很重要
既然大家看到了渲染循环是如何工作的 我们来深入几种可能在 app 上 看到的卡顿类型
发生卡顿的类型?
提交卡顿发生在 app 的处理中 commit hitch
提交卡顿就是 app 花费过长时间 来处理或提交事件
这个提交用了太长时间,错过了截止期,所以在下一个 VSYNC渲染服务区没有东西处理,所以现在必须等待下一个 VSYNC 开始渲染,现在我们把帧传送时间推迟了一帧,以毫秒计时 这是 iPhone 或 iPad 上的 16.67 毫秒
如果提交工作花了更长的时间 通过了下一个 VSYNC 那么这一帧就晚了两帧 或者说是 33.34 毫秒在这 33.34 毫秒中,用户都无法得到顺畅的滚动
发生在渲染服务器 Render hitch
渲染卡顿 这种现象会在渲染服务器无法 按时准备或者 执行图层树时出现
在这里 显然执行阶段时间过长 超过了 VSYNC 的界限 因此这一帧无法按时准备好 绿色的画面比预期的晚了一帧
于是我们又有了 16 毫秒的卡顿时间
如何测量卡顿?
检测卡顿指标 卡顿时间比 Hitch time ratio
解释: 标准化为总时间 我们就能在不同的实践中交叉比较 它是由每秒中的卡顿毫秒时间来测定的 所以它代表着设备在每秒内出现卡顿的 毫秒数
举🌰说明
在这里我们可以看到 30 帧 在一台 iPhone 上这是半秒的工作量
每一帧都到达了限定期限 用户看不到卡顿 卡顿时间为零 卡顿时间比也为零
现在我们看到在底部显示条上的 间隔不相交区间,发生卡顿的帧
有些在屏幕上的帧比其他的长,有些提交和渲染造成了卡顿,如果我们将这个卡顿时间加起来,结果就是 100.02 毫秒 超过了半秒,我们就得到了每秒 200.04 毫秒的卡顿时间比,
这仅仅是一个例子,大体上来说,这些是建议的目标卡顿时间比,这是我们在 Apple 的工具中使用的,虽然目标是零毫秒每秒的卡顿
1.五毫秒每秒以下的卡顿 -> 不易被用户察觉到的
2.在五到十毫秒每秒的卡顿之间->用户就会察觉到一些中断
3.超过 10 毫秒每秒的卡顿 -> 这些卡顿就会严重影响用户体验
你应该立即研究如何优化渲染循环
本篇结束.
后续请留意关注,检测卡顿的方式.