Skip to main content

性能最佳实践

通常情况下,Flutter 应用默认情况下性能良好,因此您只需要避免常见的陷阱即可获得卓越的性能。这些最佳实践建议将帮助您编写性能最佳的 Flutter 应用。

您如何设计 Flutter 应用以最有效地渲染您的场景?特别是,您如何确保框架生成的绘图代码尽可能高效?一些渲染和布局操作已知比较慢,但并非总是可以避免。应谨慎使用这些操作,遵循以下指南。

最小化昂贵的操作

#

有些操作比其他操作更昂贵,这意味着它们消耗更多资源。显然,您只想在必要时才使用这些操作。您设计和实现应用 UI 的方式会对它的运行效率产生重大影响。

控制 build() 成本

#

在设计 UI 时,请记住以下几点:

  • 避免在 build() 方法中进行重复且代价高昂的工作,因为当祖先小部件重建时,build() 可能会频繁调用。
  • 避免使用包含大型 build() 函数的过大的单个小部件。根据封装以及它们如何变化将它们拆分成不同的部件:
    • 当调用 State 对象上的 setState() 时,所有后代小部件都会重建。因此,将 setState() 调用定位到 UI 实际需要更改的子树部分。如果更改仅限于树的一小部分,则避免在树的较高位置调用 setState()
    • 当遇到与前一帧相同的子部件实例时,重建所有后代的遍历就会停止。此技术在框架内部大量用于优化动画,其中动画不会影响子树。请参阅 TransitionBuilder 模式和 SlideTransition 的源代码,它使用此原理在动画时避免重建其后代。(“相同实例”使用 operator == 进行评估,但请参阅此页面末尾的陷阱部分,了解何时避免重写 operator == 的建议。)
    • 尽可能使用小部件的 const 构造函数,因为它们允许 Flutter 避免大部分重建工作。为了在可能的情况下自动提醒您使用 const,请启用 flutter_lints 包中推荐的 lint。有关更多信息,请查看 flutter_lints 迁移指南
    • 要创建可重用的 UI 片段,最好使用 StatelessWidget 而不是函数。

有关更多信息,请查看:


谨慎使用 saveLayer()

#

一些 Flutter 代码使用 saveLayer()(一项昂贵的操作)来实现 UI 中的各种视觉效果。即使您的代码没有显式调用 saveLayer(),您使用的其他小部件或包也可能在后台调用它。也许您的应用调用 saveLayer() 的次数超过了必要;过度调用 saveLayer() 会导致卡顿。

为什么 saveLayer 昂贵?

#

调用 saveLayer() 会分配一个离屏缓冲区,并且将内容绘制到离屏缓冲区可能会触发渲染目标切换。GPU 想要像消防水龙一样运行,而渲染目标切换会迫使 GPU 暂时重定向该流,然后再次将其定向回来。在移动 GPU 上,这对渲染吞吐量尤其具有破坏性。

何时需要 saveLayer

#

在运行时,如果您需要动态显示来自服务器的各种形状(例如),每个形状都具有一定的透明度,并且可能(也可能不)重叠,那么您几乎必须使用 saveLayer()

调试对 saveLayer 的调用

#

您如何确定您的应用直接或间接调用 saveLayer() 的频率?saveLayer() 方法会在 DevTools 时间轴 (/tools/devtools/performance#timeline-events-tab) 上触发一个事件;通过检查 DevTools 性能视图 (/tools/devtools/performance) 中的 PerformanceOverlayLayer.checkerboardOffscreenLayers 开关来了解您的场景何时使用 saveLayer

最小化对 saveLayer 的调用

#

您可以避免调用 saveLayer 吗?这可能需要重新考虑您创建视觉效果的方式:

  • 如果调用来自 您的 代码,您可以减少或消除它们吗?例如,也许您的 UI 重叠了两个形状,每个形状都具有非零透明度:

    • 如果它们总是以相同的量、相同的方式、相同的透明度重叠,您可以预先计算这个重叠的半透明对象的外观,将其缓存,然后使用它而不是调用 saveLayer()。这适用于您可以预先计算的任何静态形状。
    • 你能否重构你的绘图逻辑以完全避免重叠?
  • 如果调用来自您不拥有的包,请联系包所有者并询问为什么需要这些调用。他们可以减少或消除吗?如果没有,您可能需要找到另一个包,或者自己编写一个。

其他可能触发 saveLayer() 并可能代价高昂的小部件:

  • ShaderMask
  • ColorFilter
  • Chip—如果 disabledColorAlpha != 0xff,则可能会触发对 saveLayer() 的调用
  • Text—如果存在 overflowShader,则可能会触发对 saveLayer() 的调用

最小化不透明度和裁剪的使用

#

不透明度是另一项昂贵的操作,裁剪也是如此。以下是一些您可能会发现有用的提示:

  • 仅在必要时才使用 Opacity 小部件。请参阅 Opacity API 页面中的 透明图像 部分,了解如何直接将不透明度应用于图像的示例,这比使用 Opacity 小部件更快。
  • 而不是将简单的形状或文本包装在 Opacity 小部件中,通常更快的是只使用半透明颜色绘制它们。(但这只有在要绘制的形状中没有重叠位的情况下才有效。)
  • 要实现图像淡入,请考虑使用 FadeInImage 小部件,它使用 GPU 的片段着色器应用逐渐不透明度。有关更多信息,请查看 Opacity 文档。
  • 裁剪不会调用 saveLayer()(除非使用 Clip.antiAliasWithSaveLayer 显式请求),因此这些操作不像 Opacity 那样昂贵,但裁剪仍然代价高昂,因此请谨慎使用。默认情况下,裁剪被禁用(Clip.none),因此您必须在需要时显式启用它。
  • 要创建具有圆角的矩形,而不是应用裁剪矩形,请考虑使用许多小部件类提供的 borderRadius 属性。

认真实现网格和列表

#

您的网格和列表的实现方式可能会导致您的应用出现性能问题。本节描述了创建网格和列表时的一项重要的最佳实践,以及如何确定您的应用是否使用了过多的布局传递。

要懒惰!

#

构建大型网格或列表时,请使用具有回调的惰性构建器方法。这确保了屏幕上只有可见部分在启动时构建。

有关更多信息和示例,请查看:

避免内在属性

#

有关内在传递如何导致性能问题的信息,请参阅 Flutter Gallery 中的 性能诊断演示

如果您的网格和列表存在性能问题,请参阅下一节。


最小化由内在操作引起的布局传递

#

如果您做过很多 Flutter 编程,您可能熟悉在创建 UI 时 布局和约束的工作方式。您甚至可能已经记住了 Flutter 的基本布局规则:约束向下传递,大小向上传递。父级设置位置。

对于某些小部件,特别是网格和列表,布局过程可能代价高昂。Flutter 努力只对小部件执行一次布局传递,但是,有时需要第二次传递(称为 内在传递 ),这可能会降低性能。

什么是内在传递?

#

例如,当您希望所有单元格都具有最大或最小单元格的大小(或需要轮询所有单元格的类似计算)时,就会发生内在传递。

例如,考虑一个大型的 Card 网格。网格应该具有大小一致的单元格,因此布局代码执行一次传递,从网格的根(在小部件树中)开始,要求网格中的 每个 卡片(不仅仅是可见卡片)返回其 内在 大小——小部件首选的大小,假设没有约束。有了这些信息,框架就会确定一个统一的单元格大小,并第二次访问所有网格单元格,告诉每个卡片使用什么大小。

调试内在传递

#

要确定您是否进行了过多的内在传递,请在 DevTools 中启用**跟踪布局选项**(默认情况下禁用),并查看应用程序的 堆栈跟踪 以了解执行了多少布局传递。启用跟踪后,内在时间轴事件将标记为 '$runtimeType intrinsics'。

避免内在传递

#

您可以通过以下几种方法来避免内在传递:

  • 预先将单元格设置为固定大小。
  • 选择一个特定的单元格作为“锚点”单元格——所有单元格的大小都相对于此单元格。编写一个自定义 RenderObject ,该对象首先定位子锚点,然后在其周围布局其他子项。

要更深入地了解布局的工作原理,请查看 Flutter 架构概述 中的 布局和渲染 部分。


在 16ms 内构建和显示帧

#

由于构建和渲染有两个单独的线程,因此对于 60Hz 显示屏,您有 16ms 用于构建,16ms 用于渲染。如果延迟是一个问题,请在 16ms 或更短 的时间内构建和显示帧。请注意,这意味着在 8ms 或更短的时间内构建,在 8ms 或更短的时间内渲染,总共 16ms 或更短的时间。

如果您的帧在 概要模式 下的总渲染时间远低于 16ms,那么即使某些性能缺陷适用,您也可能不必担心性能,但您仍然应该争取以尽可能快的速度构建和渲染帧。为什么?

  • 将帧渲染时间降低到 16ms 以下可能不会产生视觉差异,但它会 延长电池寿命 并解决散热问题。
  • 它可能在您的设备上运行良好,但请考虑您目标的最低设备的性能。
  • 随着 120fps 设备的日益普及,您需要在 8ms(总计)内渲染帧才能提供最流畅的体验。

如果您想知道为什么 60fps 会带来流畅的视觉体验,请查看视频 为什么是 60fps?

陷阱

#

如果您需要调整应用的性能,或者 UI 没有您预期的那么流畅,则 DevTools 性能视图 可以提供帮助!

此外,您 IDE 的 Flutter 插件也可能很有用。在 Flutter 性能窗口中,启用 显示小部件重建信息 复选框。此功能可帮助您检测帧何时以超过 16ms 的时间渲染和显示。在可能的情况下,插件会提供指向相关提示的链接。

以下行为可能会对您的应用性能产生负面影响。

  • 避免使用 Opacity 小部件,尤其是在动画中避免使用它。改用 AnimatedOpacityFadeInImage。有关更多信息,请查看 不透明度动画的性能注意事项

  • 使用 AnimatedBuilder 时,避免在构建器函数中放置构建不依赖于动画的小部件的子树。此子树会为动画的每个刻度重建。相反,构建子树的那一部分,并将其作为子项传递给 AnimatedBuilder。有关更多信息,请查看 性能优化

  • 避免在动画中进行裁剪。如果可能,请在动画之前预先裁剪图像。

  • 如果屏幕上大部分子项不可见,则避免使用具有具体子项 List 的构造函数(例如 Column()ListView()),以避免构建成本。

  • 避免重写 Widget 对象上的 operator ==。虽然看起来它可以通过避免不必要的重建来提供帮助,但在实践中它会损害性能,因为它会导致 O(N²) 行为。此规则的唯一例外是叶小部件(没有子项的小部件),在这种情况下,比较小部件的属性可能比重建小部件更有效,并且小部件很少更改配置。即使在这种情况下,通常也最好依赖于缓存小部件,因为即使重写一次 operator == 也会导致全面的性能下降,因为编译器不再能够假设该调用始终是静态的。

资源

#

有关更多性能信息,请查看以下资源: