大多数浏览器和
Developer App 均支持流媒体播放。
-
在提交阶段里找出和修复阻碍
发现如何通过解决渲染循环里的提交阶段的问题,在你 App 里更顺畅地渲染动画。深入研究其机械原理,了解如何使用工具来发现你 App 停滞的源头并解决它,并马上避免再发生。
资源
相关视频
Tech Talks
-
下载
(提交时找到并解决卡顿) 你好 我叫 Charles Circlaeys 在这个讲座中 我们会深入讲解渲染循环提交阶段中 出现的滚动动画卡顿
iOS 使用渲染循环来显示视图 触碰事件被发送至你的 app app 通过改变视图来进行反馈 而这些视图则由 iOS 渲染到显示上
我们将着重讲解 如何在渲染循环的提交阶段 发现和解决动画卡顿 想要了解整个渲染循环和什么是卡顿 请前往观看《探索用户界面 动画卡顿和渲染循环》视频
我们首先了解一下提交事务的定义
使用 Instruments 来找到卡顿
我们还会分享一些 如何避免提交卡顿的建议 我们先定义提交事务 这是一个 app 视图层次结构示例 正在等待事件来触发 当它收到一个触碰事件时 其中的一个视图就会做出反应 并通过改变背景颜色 或改变某些子视图的框来处理该事件 在下一次提交事务时 系统记录这些子视图将需要 某个布局或显示
在提交事务时 这些需要某个显示或布局的视图 会通过调用 drawRect 或 layoutSubviews 来进行相应的更新
我们来看一下 在一次提交事务中涉及的不同阶段 一共有四步: 布局阶段、显示阶段 筹备阶段和最后的提交阶段
在布局阶段 layoutSubviews 将会调用 所有需要布局的视图 你可以通过改变某个视图的位置 添加或移除视图 或为某个视图调用 setNeedsLayout 来标记需要的布局
在显示阶段 drawRect 将会调用所有需要显示的视图 你可以通过在视图层次结构中 添加覆盖 drawRect 的视图 或直接调用 setNeedsDisplay 来表明需要显示 在筹备阶段 还没有解码的图像 将会在这一步进行解码 对于较大的图像来说 这样的操作会花很长时间
若某个图像的颜色格式 图形处理器无法直接使用 也会在这一步进行转换 这就要求对某个图像进行复制 而不是将光标直接发送到原图上 这样耗时更长、使用的内存更多 若想进一步了解如何优化 app 中的图像 可以观看《图像和图形最佳做法》的视频
最后 在提交阶段中 视图层次结构将被递归打包 并发送到渲染服务器 要注意的是 深层视图层次结构打包时间要长一些 我们聊完了提交事务的细节 接下来到了我们第二个话题: 用 Instruments 找卡顿 在 Xcode 12 里 我们发布了一个新的 Instrument 模板 来分析用户的 app 中的卡顿 这将有助于直观地调查 渲染循环中发现的卡顿框
我们看看示例 app 中的一些卡顿 在 app 中上下滚动时 Instruments 将会记录踪迹
这个就是反映了滚动性能的踪迹记录 这里我们可以看到发现的所有卡顿
我们来仔细看一下卡顿 16
在左边我们可以看到 与组成架构需要的渲染循环阶段 相对应的所有轨迹
卡顿轨迹则显示了 卡顿及其持续的时长
用户事件轨迹显示了 触发了卡顿框的用户事件
提交轨迹显示了提交阶段 及在这一阶段中提交的进程
在《查明并消除渲染阶段的卡顿》视频中 Patrick 会进一步讲解 渲染器和图形处理器轨迹
帧生命周期轨迹显示了 构成卡顿框的全过程 内置显示轨迹显示了 随着 VSYNC 事件出现在显示上的框
你可以对比 框的生命周期和卡顿持续时间的起始 这样可以直观地看到 框应该显示的预期间隔
我们把这个间隔称为可接受延迟 这个间隔之后均为卡顿持续时间
选择了卡顿轨迹后 可以在轨迹下面看到卡顿的详细指标 在我们示例 app 中有很多卡顿 但我们就只看卡顿 16
我们可以看到卡顿持续时长 可接受延迟
和卡顿类型 从卡顿类型中 可以推测出框在哪个阶段被延迟了 和从哪里开始调查 在这个例子中 我们可以看到选中的卡顿框 是由提交和图形处理器阶段造成的
我想知道是哪段代码在提交阶段 花费了较长的时间 谢天谢地 动画卡顿模板中 包括了时间分析器 这样我们就可以看到 是在运行哪段代码的时候出现了这个卡顿 从这里 我可以选择想要调查的间隔 并搜索当时提交的进程 我可以选择这个进程的主线程
并显示它的调用树
现在我们就可以分析 哪个调用代价过高了 我们可以看到这个调用树 是由一个提交事务发起的 它显示了我们在 QSTEM CollectionViewCell 中 一个叫 updateTags 的方法上 花费了大概 10 毫秒 我们来看看这个 app 里有什么
这个 app 是由一个常见的 CollectionView 组成的 每个单元都通过 UIImageView 使用了 UILabel 的文本 并使用了自定义 TagLabel 视图的标签 来展示一张缩略图 我们来看看 QSTEM CollectionViewCell 类的实现 和这个方法是在哪调用的 这里可以看到 我们在 menuItem 上有一个属性观测器
这个属性观测器会在两种情况下 调用 updateTags 设了一个有效的 menuItem 时 和将其设为空值时 在第一种情况下 我们解析了想要显示的一组标签数组 在第二种情况下 我们解析了一组空的数组 来移除所有剩余的标签 我们现在来看一下 updateTags 方法的实现 和预想的一样 为防止出现空的标签数组 我们把视图层次结构中所有视图都移除了 或者在需要的时候创建一个 StackView
然后我们为每个标签 都创建一个 TagLabels 或再利用现有的 之后 把当前所有空闲的 TagLabels 都移除 以防之前这个单元的使用情况 比新的这个的标签还要多 我们回到调用范围 看看是否有潜在的问题 我们现在知道了是如何实现的 QSTEM CollectionViewCell 覆盖了 当某一单元被移除复用序列时调用的 prepareForReuse 方法 在这个方法里 我们将 menuItem 的值设置为空 这样就触发了第二种情况 从单元视图层次结构中移除了 之前所有的 TagLabels 也没有使用我们复用的实现逻辑
这就意味着 我们将会移除 每个非序列的单元中所有标签子视图 并将显示所有标签所需的视图 都重新实例化
这种做法不是最佳做法 可能会导致卡顿产生 解决方法也很简单 我们不需要清空我们的 menuItem 只要从实现中 移除 prepareForReuse 方法就好 现在 当我们设置新的单元 menuItem 时 我们就可以用上复用逻辑 同时避免代价过高的视图层级结构操作 在修改之后再记录新的轨迹 就会发现与第一个轨迹相比 卡顿的数量大幅减少 Instruments 中的时间分析器 非常有利于帮助我们找到耗时长 造成卡顿的代码 想要了解更多时间分析器的信息 大家可以观看《在 Instruments 中 使用时间分析器》的视频
在学习了 如何使用 Instruments 分析应用后 我们来聊一聊如何在提交阶段 避免出现卡顿的建议 第一条规则 保持视图的轻量 为了保持视图的轻量 尽可能地利用 CALayer 上图形处理器加速的可用属性 并避免使用中央处理器自定义绘图 若一定要用 切记要测量其性能 因为系统要做额外工作 从而需要在处理下一事务时 使用更长的耗时 并占用更多内存 所以要避免出现空的 drawRect 实现 尽量复用视图 避免使用代价过高的视图层级结构操作 如添加和移除 移除视图时 如果要把某一视图从某一动画中移除 尽量使用视图属性“ 隐藏” 这个操作的代价就小多了 第二条规则 减少代价过高且重复的布局 在需要更新布局时 尽量只使用 setNeedsLayout layoutIfNeeded 会消耗 当前事务的生命周期 也会造成卡顿 大多数时候 你可以等到下一次循环执行时 再更新你的布局 试着使用最少的限制 来避免解决问题时增加难度 最后 视图应该只能使自己 或自己的子视图无效 而不能使其同级视图或父视图无效 不然的话 视图的布局就会再一次陷入递归性无效 我建议大家观看下面的两个 WWDC 视频 来了解更多关于性能布局 及图像和图形最佳做法的信息
现在大家了解了提交事务的管线 就可以避免代价过高的提交 大家可以使用 Instruments 中 最新的动画卡顿模板 来找到和调查卡顿 大家也学到了防止提交卡顿出现的策略 如确保 prepareForReuse 不会产生额外的工作 让你的视图层级结构保持简单轻量 并避免代价过高和重复的布局 此外 大家一定要在 《查明并消除渲染阶段的卡顿》视频中 了解渲染循环的下一阶段 谢谢
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。