跳至主要内容

性能最佳实践

通常情况下,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 时间线上触发一个事件;通过检查DevTools 性能视图中的PerformanceOverlayLayer.checkerboardOffscreenLayers开关来了解您的场景何时使用saveLayer

最大程度减少对 saveLayer 的调用

#

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

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

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

其他可能触发saveLayer()且可能代价高昂的组件

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

最大程度减少不透明度和裁剪的使用

#

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

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

精心实现网格和列表

#

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

要懒惰!

#

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

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

避免内在属性

#

有关内在属性传递如何可能导致网格和列表出现问题的更多信息,请参阅下一节。


最大程度减少由内在操作引起的布局传递

#

如果您已经进行过很多 Flutter 编程,那么您可能熟悉在创建 UI 时布局和约束的工作原理。您甚至可能已经记住了 Flutter 的基本布局规则:**约束向下传递。尺寸向上传递。父组件设置位置。**

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

什么是内在属性传递?

#

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

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

调试内在属性传递

#

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

避免内建布局传递

#

您可以选择以下几种方法来避免内建布局传递

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

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


在 16 毫秒内构建和显示帧

#

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

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

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

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

陷阱

#

如果您需要调整应用的性能,或者 UI 没有达到您的预期流畅度,DevTools 性能视图可以帮到您!

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

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

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

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

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

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

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

资源

#

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