性能最佳实践
通常情况下,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
而不是函数。
- 当调用
有关更多信息,请查看:
StatefulWidget
API 文档中的一部分 性能注意事项- 小部件与辅助方法,来自官方 Flutter YouTube 频道的视频,解释了为什么小部件(尤其是具有
const
构造函数的小部件)比函数性能更高。
谨慎使用 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
属性。
认真实现网格和列表
#您的网格和列表的实现方式可能会导致您的应用出现性能问题。本节描述了创建网格和列表时的一项重要的最佳实践,以及如何确定您的应用是否使用了过多的布局传递。
要懒惰!
#构建大型网格或列表时,请使用具有回调的惰性构建器方法。这确保了屏幕上只有可见部分在启动时构建。
有关更多信息和示例,请查看:
- Cookbook (/cookbook) 中的 处理长列表
- AbdulRahman AlHamali 编写的社区文章 创建一次加载一页的
ListView
Listview.builder
API
避免内在属性
#有关内在传递如何导致性能问题的信息,请参阅 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
小部件,尤其是在动画中避免使用它。改用AnimatedOpacity
或FadeInImage
。有关更多信息,请查看 不透明度动画的性能注意事项。使用
AnimatedBuilder
时,避免在构建器函数中放置构建不依赖于动画的小部件的子树。此子树会为动画的每个刻度重建。相反,构建子树的那一部分,并将其作为子项传递给AnimatedBuilder
。有关更多信息,请查看 性能优化。避免在动画中进行裁剪。如果可能,请在动画之前预先裁剪图像。
如果屏幕上大部分子项不可见,则避免使用具有具体子项
List
的构造函数(例如Column()
或ListView()
),以避免构建成本。避免重写
Widget
对象上的operator ==
。虽然看起来它可以通过避免不必要的重建来提供帮助,但在实践中它会损害性能,因为它会导致 O(N²) 行为。此规则的唯一例外是叶小部件(没有子项的小部件),在这种情况下,比较小部件的属性可能比重建小部件更有效,并且小部件很少更改配置。即使在这种情况下,通常也最好依赖于缓存小部件,因为即使重写一次operator ==
也会导致全面的性能下降,因为编译器不再能够假设该调用始终是静态的。
资源
#有关更多性能信息,请查看以下资源:
- AnimatedBuilder API 页面中的 性能优化
- Opacity API 页面中的 不透明度动画的性能注意事项
- ListView API 页面中的 子元素的生命周期 以及如何有效地加载它们
StatefulWidget
的 性能注意事项- 优化 Flutter Web 加载速度的最佳实践
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。