大多数浏览器和
Developer App 均支持流媒体播放。
-
在渲染阶段里阐明和消除阻碍
当你在 App 里执行复杂的视图层次,你可能会遇到动画停滞。揭秘视图是如何在渲染阶段里变成像素,和了解如何用工具在这部分的渲染循环里去找出问题。发掘如何消除离屏的通过与利用 Xcode 优化机会在你使用 App 时去提供一个更好的体验。
资源
相关视频
WWDC23
Tech Talks
-
下载
(查明并消除渲染阶段的卡顿) 嗨 我叫 Patrick 来自 Apple 的 OS 性能团队 今天我们将深入查明 并消除 app 中的渲染卡顿
iOS 使用渲染循环来显示视图 在渲染循环没能及时完成一个帧 以进行显示时 就会出现卡顿
对于整个渲染循环的概述 请查看我的《探索用户界面动画卡顿 和渲染循环》讲座 这里我们将重点讨论渲染卡顿 即由于渲染准备和渲染执行阶段的缓慢 而导致的卡顿
首先 我们来探讨这两个渲染阶段是什么 然后探讨如何用 Instruments 和 Xcode 视图调试器 在 app 中捕获并鉴别渲染卡顿 最后 我们会看一些建议 以优化我们的图层树 并防止卡顿干扰用户体验 让我们从定义渲染阶段开始 在提交阶段 app 修改其用户界面 并提交更新的用户界面图层树以进行处理 我们将其称为“提交” 渲染服务器负责渲染 所有前台进程的提交 如果在渲染服务器中的处理过程 超过一帧的持续时间 则可能会出现卡顿 尽管该工作发生在 app 的进程之外 但渲染工作是代表你的 app 完成的 因此你要负责 渲染你的 app 的图层树需要多长时间 渲染服务器有两个阶段: 渲染准备和渲染执行 在渲染准备阶段 我们的图层树被编译成 一系列简单的操作 供图形处理器执行 在几个帧上发生的动画 也在此处进行处理 在渲染执行阶段 图形处理器将 app 的图层 绘制成最终图像 准备显示 这两个阶段都可能延迟帧的交付时间
为了帮助理解这些概念 我们来看看一个示例渲染的过程 我们会看一下这一帧的渲染过程 请注意 在圆形和长条周围都有阴影 这在之后会很重要 我们从 app 提交到左侧渲染服务器的 图层树开始 渲染服务器会逐层编译一系列绘图命令 使图形处理器能从后向前绘制用户界面
从根节点开始 渲染服务器从同级到同级 从父级到子级 直到涵盖层级中的每个图层
最终 它有了图形处理器可以 在下一个执行阶段执行的整个管线 图形处理器的工作是利用该管线 将每一步都绘制成中间的最终纹理 在显示阶段 会在屏幕上显示该纹理 从第一个蓝色的图层开始 它在指定的边界内绘制颜色 接下来 较深的蓝色被绘制在其边界内 然后我们继续绘制下一个图层
但现在 图形处理器必须绘制阴影 阴影的形状由下面两个层定义 因此图形处理器不知道 用什么形状来绘制阴影 但如果我们先绘制圆形和长条 那么阴影会用黑色遮挡它们 看起来会不正确 这意味着图形处理器遇到了障碍 要继续 它必须切换到不同的纹理 以确定阴影的形状 我们称之为“离屏渲染” 因为我们在最终纹理以外绘制 从这里 它可以绘制圆形和长条 现在 它将阴影形状隔离在离屏纹理内 通过先将图层变成黑色 然后将其模糊 它具备了绘制阴影形状所需的所有条件
然后 它可以将那个离屏纹理 复制到最终纹理中 阴影图层就完成了
下一步是再次绘制圆形和矩形 它会通过复制 app 在上面绘制的文本的图像来完成
我们现在完成了两个渲染阶段 并且帧准备好进行显示了 但我们不得不 用一个特殊的技巧来渲染阴影 这导致渲染需要更长的时间 这称为离屏通道
离屏通道是指图形处理器 必须先在其它地方渲染一个图层 然后再将其复制过来 就阴影而言 它必须绘制图层 以确定最终形状 离屏通道可能会积少成多 导致渲染出现卡顿 因此在 app 中监控 并尽量避免它们很重要
有四种主要类型的离屏通道可以优化: 阴影、蒙版、圆角矩形和视觉效果 我们在示例渲染中 看到了一个阴影离屏的例子 在本例中 不先绘制附加到它上面的图层 渲染器就没有足够的信息来绘制阴影
第二种类型的离屏 是当图层或图层树需要遮蔽时 渲染器需要渲染被遮蔽的子树 但它也需要避免覆盖 被遮蔽形状之外的像素 因此它只会在被屏蔽形状内的像素 复制回最终纹理之前 渲染整个子树 这种离屏可能会导致渲染 许多用户永远不会看到的像素
第三种类型与蒙版有关 将图层的角圆化有时可能需要离屏 如果没有提供足够的信息 渲染器可能必须离屏绘制整个视图 然后将圆化形状内的像素复制回来
第四种类型来自视觉效果视图 用户界面套件提供两种视觉效果类型: 鲜亮化和模糊 要应用这些效果 渲染器必须用离屏通道 将视觉效果视图下面的内容 复制到另一个纹理 然后将视觉效果类型应用到结果 并将其复制回来 你会在用户界面导航栏 用户界面标签栏 和许多其它标准控件中看到这个 因为这在 iOS、tvOS 和 macOS 中非常常见
这四种离屏类型可能会降低渲染速度 并导致渲染卡顿
现在我们说明了渲染阶段的详情 并了解了大量离屏通道 如何能导致这些问题 让我们转到第二个话题: 用 Instruments 搜索卡顿
在 Instruments 12 中 我们发布了一个新的 Instrument 模板 用于对你的 app 中的卡顿进行分析
一些用户抱怨过膳食计划 app 中的卡顿 我希望进行调查 因此我开始使用 Instrument 并开始在 app 中滚动
这是使用动画卡顿模板 获得的膳食计划 app 的轨迹 我很好奇地想深入了解 在滚动时看到的一些卡顿 让我们放大并展开卡顿轨迹 以搜索卡顿 16
每个轨迹对应于 我们之前谈到的渲染循环的一个阶段 在顶端是最重要的轨迹 它显示卡顿间隔 这是帧应该已经准备好的时间
用户活动轨迹显示 与卡顿帧相关联的用户活动
提交轨迹显示在该帧期间 发送到渲染服务器的所有提交阶段 请一定要观看 《搜索并修复提交阶段的卡顿》 以了解更多关于这些轨迹的特定信息
以下是我们在本视频中重点介绍的内容 渲染器和图像处理器轨迹 显示渲染服务器执行的工作
帧生命周期轨迹显示 构成帧所需的整个持续时间 从活动到显示 最后 内置显示轨迹 显示所有出现在显示屏上的帧 以及在该过程中发生的 VSYNC
你可以将帧生命周期 与卡顿持续时间的起始进行比较 以可视化 帧应该完成构成的预期间隔 在该卡顿中 我们经过了两帧 通过追踪 VSYNC 我们可以看到提交和渲染阶段都超时了
这个间隔被称为可接受延迟 之后的所有时间均为卡顿持续时间
在轨迹下方 在选择卡顿轨迹时 我们会看到卡顿的详细指标 我们在审视卡顿 16 我们可以看到卡顿时间和可接受延迟 这是我们完成该帧所需的时间
缓冲区计数是发生卡顿时 渲染服务器使用的缓冲区数量 默认值为 2 但在渲染帧被延迟 且渲染服务器在试图追赶时可以为 3 在双缓冲模式下 我们有两帧 或者在 iPhone 上为 33.34 毫秒 这是我们在延迟列中看到的 始终记住遵循上面的卡顿持续时间轨迹 这会始终突出显示关注的区域 无论缓冲区计数是多少 最后是卡顿类型 卡顿类型显示卡顿的类型 并帮助你了解 app 中要深入研究的东西 在这里 我们看到代价过高的 提交和图形处理器时间 这是我们在上面的轨迹中看到的 展开后 我们可以将注意力集中在 渲染和图像处理器轨迹上 并选择它们 以查看包含更多 关于准备和执行阶段信息的分析器
一个关键的列是渲染计数 在这里我们可以看到 图像处理器必须创建的 离屏通道的数量 因为我们知道有渲染卡顿 我们需要查看这些离屏 了解造成它们的原因以及如何修复 查看图层树的最佳方式是 使用 Xcode 视图调试器 为此 让我们来看一个演示
我们在视图调试器中暂停了膳食计划 app 在左侧 我们可以看到视图控制器 窗口、约束和视图 但从 Xcode 11.2 开始 我们也可以显示图层 如果我们点击编辑器 让我们点击新的显示图层项目
酷 现在 在左侧的导航器中 我们可以点击任何视图 看到它的图层和所有子层
在选择一个图层时 我们会看到这个全新的图层 inspector 它提供我们的图层的有用属性 我们可以看到 标签视图支持图层 我们可以看到背景颜色、不透明度 是否启用了 masksToBounds 和许多其它属性 重要的是 我们可以看到离屏计数 这是渲染该图层所需的离屏数量 下面是我们所说的离屏标志 这些说明离屏的原因 特定的标志 例如离屏蒙版 可能会触发多个离屏 例如 这里我们有两个 但即便我们要找遍整个 app 的每一层 来查看它的离屏计数 我们仍然不会有足够的深入了解 来减少这些离屏通道 为了帮助识别和建议 视图和图层层级中的性能优化 我们在 Xcode 12 中添加了 一个新的运行时问题类型 我们称之为优化机会
这些是默认启用的 但你可以在编辑器菜单中的 显示优化机会下找到该选项 这些优化机会是 Apple 的性能团队 在多年优化 app 的渲染性能后 开发和编写的令人难以置信的资源 这些旨在建议简单但有价值的更改 不会影响你的图层的整体外观 在导航器中 我们可以在一些图层上 看到紫色的运行时问题指示器
这里是我们的星形图层 我们在 inspector 中看到 它需要 5 个离屏 在指示器上突出显示可以揭示 其原因是动态阴影 让我们转至运行时问题导航器 以查看更多详细信息
在这里 我们可以读取关于该问题的信息 它说图层使用的是动态阴影 渲染的代价很高 如果可能的话 尝试设置 shadowPath 或将阴影预渲染到图像中 并将其置于图层下方
我们在幻灯片中讨论这种类型的离屏 渲染器没有足够的信息 需要离屏绘制图层以确定阴影的形状 通过在 CALayer 上 使用 shadowPath 属性 我们可以为渲染器提供 使用并去除所有五个离屏的确切步骤
这些在我们的 app 中积少成多 因此让我们看看代码并进行更改
这里是我们的星形图层 我们在那里设置阴影 优化文本告诉我们设置 接受任何 CGPath 的 shadowPath 让我们重复使用已经创建的星形路径
就这样 我们去除了 每个 CollectionViewCell 的 5 个离屏通道 这是一个很大的改进
现在回到调试导航器 我们仍然看到一些其它的 运行时问题指示器 在标签视图 我们看到图层上有一个运行时问题 在导航器中 我们看到两个由离屏蒙版造成的离屏 该图层由一个图像视图 和一个标签视图组成 我们担心有东西脱离红色背景 因此我们添加了蒙版图层 在运行时工具提示中 我们看到原因是简单背景颜色遮蔽
在问题导航器中 我们可以看到机会文本 它说该图层使用的是简单图层 背景色设置为蒙版 取而代之 使用与蒙版具有相同帧和圆角半径的 容器图层 两个 masksToBounds 均设置为“Yes” 离屏的原因是 渲染器需要先渲染我们的蒙版图层 建议是完全去除该图层 让我们在代码中看一下
这里是我们的标签视图 我们创建一个蒙版图层 给它一个黑色的背景颜色和圆角半径 这个蒙版图层很简单 而任何简单图层都不应该是蒙版 相反 我们应该在实际图层上 定义这个请求的蒙版形状 以便渲染器可以优化绘图 我们可以像我们希望的那样 将圆角半径设置为 10 然后将 masksToBounds 设置为等于“true” 然后完全删除蒙版
但这只能去除一个离屏 因为我们有子层 masksToBounds 需要创建一个离屏通道 以确保视图被正确剪裁 但在我们看来 我们已经确保子层无法超出 标签视图的边界 因此实际上我们根本不需要遮蔽 让我们删除这个 masksToBounds 调用 现在我们去除了两个离屏 太棒了 到目前为止 在这三行代码中 我们为每个 CollectionViewCell 减少了七个离屏 这是一个巨大的改进 但是我们还有一个问题要探讨 让我们回到导航器
让我们选择图像视图 我们看到它有圆角 在我们突出显示运行时问题时 我们看到它说“简单形状遮蔽” 再次 我们将转至运行时导航器 以了解更多信息
它说图层被一个 有矩形、圆角矩形或椭圆形路径的 CAShapeLayer 遮蔽了 取而代之 使用适当转换的容器图层 并设置 cornerRadius 和 masksToBounds 离屏的原因是 渲染器需要再次蒙版到另一个图层 但这次不是简单图层 让我们看看代码以了解发生了什么
在代码中 我们可以看到图像视图 首先 我们创建 CAShapeLayer 并使用 UIBezierPath API 创建路径 然后用其作为蒙版 我们使用形状图层 它可以是有效的蒙版类型 但 Xcode 能检测到 我们只是在创建圆角矩形 而不是复杂形状 我们编写这段代码的原因是 我们试图创建一种特殊类型的圆角矩形 有时也称为方圆形 这种类型的圆角矩形在 iOS 中非常流行 但这不是获得这种效果的最佳方式 从 iOS 13 开始 我们可以使用 cornerCurve 属性 将 cornerRadius 效果变成 椭圆形矩形形状 现在我们可以完全去除 shapeLayer 并将 cornerRadius 和 cornerCurve 设置为“连续”
就这样 我们仅用所提供的 API 就又去除了两个离屏 我们能真正优化离屏计数 将其从 36 降到零 这太不可思议了
回到视图调试器 我们看到一些与渲染无关的 其它运行时问题 尽管如此 我们想把它们 让我们的同事 Charles 看一下 以便他能尝试进行优化 以前 这需要 Charles 有和我们同样的设备 连接到他的 app 并在视图调试器中暂停 但现在我们能保存视图调试器的状态 这样就可以通过电子邮件发送 将其与同事共享 甚至将其附加到反馈报告中 为此 我们可以选择文件 导出视图层级…
点击“保存” 然后就好了 现在我们有一个可发送的文件 封装了我们所有的视图、图层 约束和运行时问题 这使远程协作更加容易 并会在尝试追踪所有不必要的离屏时 非常有帮助
现在我们了解了 Xcode 视图调试器 如何突出显示我们的 app 中的离屏问题 让我们回顾一些建议
在每个 app 中 最重要的是 始终使用所提供的 API 在设置阴影时 确保设置 shadowPath 以减少大量离屏通道 在圆化矩形时 使用 cornerRadius 和 cornerCurve 属性 避免用蒙版或角内容来构成圆角矩形形状 这些会导致不必要的离屏
对于大多数图层 只需将图层边界舍入到其 cornerRadius 以创建 UIBezierPath 即可创建一个很棒的 shadowPath
第二步是优化整个 app 的遮蔽 使用 masksToBounds 遮蔽为矩形 圆角矩形或椭圆形 它的性能比自定义蒙版图层好得多 总之 确保遮蔽是必需的 如果子树中的内容不会超出边界 则完全禁用 masksToBounds
这些只是建议 重要的是用 Instruments 来对你的 app 进行分析 并用优化机会来检查你的图层树 以获得简单而重要的技巧 从而降低你的整体离屏计数 最后 记住可以保存视图层级的状态 以与团队共享 甚至在 app 的反馈报告中附加反馈 总之 这些工具和建议 会帮助你避免渲染卡顿 对于提交卡顿 请确保查看 《搜索并修复提交阶段的卡顿》 这些讲座会共同帮助你 降低 app 的卡顿时间比率 并使用户流畅地滚动
感谢观看
-
-
0:01 - Shadow Path
// Setup shadow properties view.layer.shadowColor = UIColor.black.cgColor view.layer.shadowOpacity = 0.5 // Set a shadow path on a basic layer view.layer.shadowPath = UIBezierPath(roundedRect: view.layer.bounds, cornerRadius: view.layer.cornerRadius).cgPath
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。