Flutter 应用性能优化最佳实践
通常来说,Flutter 构建的应用程序在默认情况下都是高性能的。所以你只需要避开常见的陷阱,不需要使用复杂的分析工具对细节做优化,就可以获得优异的性能,这些最佳建议将帮助你编写性能最佳的 Flutter 应用程序。
Generally, Flutter applications are performant by default, so you only need to avoid common pitfalls to get excellent performance. These best recommendations will help you write the most performant Flutter app possible.
如果你在用 Flutter 编写 Web 应用,你可能会对下面的系列文章感兴趣,他们由 Flutter Material 团队撰写,记录了对 Flutter Gallery 应用的修改,使其在 Web 上的性能更好:
If you are writing web apps in Flutter, you might be interested in a series of articles, written by the Flutter Material team, after they modified the Flutter Gallery app to make it more performant on the web:
-
Optimizing performance in Flutter web apps with tree shaking and deferred loading
最佳实践
Best practices
如何设计一个能最有效地渲染页面的 Flutter 应用程序?特别是如何确保底层框架生成的绘图代码尽可能高效?这里有几件需要你在设计应用时考虑的事情:
How do you design a Flutter app to most efficiently render your scenes? In particular, how do you ensure that the painting code generated by the framework is as efficient as possible? Here are a few things to consider when designing your app:
控制 build() 方法的耗时
Controlling build() cost
-
避免在
build()
方法中进行重复且耗时的工作,因为当父 widget 重建时,子 Wdiget 的build()
方法会被频繁地调用。Avoid repetitive and costly work in
build()
methods sincebuild()
can be invoked frequently when ancestor Widgets rebuild. -
避免在一个超长的
build()
方法中返回一个过于庞大的 widget。把他们分拆成不同的 widget,并进行封装,另外他们要这样改变:Avoid overly large single Widgets with a large
build()
function. Split them into different Widgets based on encapsulation but also on how they change:-
当在 State 上调用
setState()
时,所有后代 widget 都将重建。因此,将setState()
的调用转移到其 UI 实际需要更改的 widget 子树部分。如果改变的部分仅包含在 widget 树的一小部分中,请避免在 widget 树的更高层级中调用setState()
。When
setState()
is called on a State, all descendent widgets rebuild. Therefore, localize thesetState()
call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree. -
当重新遇到与前一帧相同的子 widget 实例时,将停止遍历。这种技术在框架内部大量使用,用于优化动画不影响子树的动画。请参阅
TransitionBuilder
模式和遵循此原则的 SlideTransition 代码,以避免在动画过程中重建其后代 widget。The traversal to rebuild all descendents stops when the same instance of the child widget as the previous frame is re-encountered. This technique is heavily used inside the framework for optimizing animations where the animation doesn’t affect the child subtree. See the
TransitionBuilder
pattern and the source code forSlideTransition
, which uses this principle to avoid rebuilding its descendents when animating.
-
请尽可能的在 widget 上使用 const
构造函数,这将让 Flutter 的 widget 重建时间大幅缩短。在构建可复用的 UI 代码时,最好使用 StatelessWidget
而不是函数。
Use const
constructors on widgets as much as possible, since
they allow Flutter to short-circuit most of the rebuild work. To create
reusable pieces of UIs, prefer using a StatelessWidget
rather than
a function.
另请参考:
Also see:
-
StatefulWidget
API 文档中的 Performance considerations 部分。Performance considerations, part of the
StatefulWidget
API doc -
Widget 与 helper 方法, Flutter YouTube 频道上发布的一个视频,解释为什么使用 widget 是更好的做法。(特别是使用
const
构造的 widget)Widgets vs helper methods, a video from the official Flutter YouTube channel that explains why widgets (especially widgets with
const
constructors) are more performant than functions.
仅当需要的时候才应用效果
Apply effects only when needed
由于代价很大,请谨慎使用效果。一些效果的背后调用了性能代价很大的 saveLayer()
方法。
Use effects carefully, as they can be expensive.
Some of them invoke saveLayer()
behind the scenes,
which can be an expensive operation.
一些在使用效果时的通用规则:
Some general rules when applying specific effects:
-
能不用
Opacity
widget,就尽量不要用。有关将透明度直接应用于图像的示例,请参见 Transparent image,这比使用 Opacity widget 更快。Use the
Opacity
widget only when necessary. See the Transparent image section in the Opacity API page for an example of applying opacity directly to an image, which is faster than using the Opacity widget. -
Clipping 不会调用
saveLayer()
(除非明确使用Clip.antiAliasWithSaveLayer
),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。Clipping doesn’t call
saveLayer()
(unless explicitly requested withClip.antiAliasWithSaveLayer
) so these operations aren’t as expensive as Opacity, but clipping is still costly, so use with caution. By default, clipping is disabled (Clip.none
), so you must explicitly enable it when needed.
其他会触发 saveLayer()
的 widget,可能也会代价高昂。
Other widgets that might trigger saveLayer()
and are potentially costly:
ShaderMask
ColorFilter
-
Chip
— 当disabledColorAlpha != 0xff
的时候,会调用saveLayer()
Chip
—might cause call tosaveLayer()
ifdisabledColorAlpha != 0xff
-
Text
— 当有overflowShader
时,会调用saveLayer()
Text
—might cause call tosaveLayer()
if there’s anoverflowShader
避免调用 saveLayer()
的方式:
Ways to avoid calls to saveLayer()
:
-
要在图像中实现淡入淡出,请考虑使用
FadeInImage
widget,该 widget 使用 GPU 的片段着色器应用渐变不透明度。了解更多详情,请参见Opacity
文档。To implement fading in an image, consider using the
FadeInImage
widget, which applies a gradual opacity using the GPU’s fragment shader. For more information, see theOpacity
docs. -
要创建带圆角的矩形,而不是应用剪切矩形,请考虑使用很多 widget 都提供的
borderRadius
属性。To create a rectangle with rounded corners, instead of applying a clipping rectangle, consider using the
borderRadius
property offered by many of the widget classes.
对列表和网格列表懒加载
Render grids and lists lazily
在构建大型网格或列表时,使用带有回调的惰性方法。这样,只有屏幕的可见部分是在开始时构建的。
Use the lazy methods, with callbacks, when building large grids or lists. That way only the visible portion of the screen is built at startup time.
请参阅:
Also see:
-
Working with long lists in the Cookbook
-
来自社区的 AbdulRahman AlHamali 撰写的文章 Creating a ListView that loads one page at a time
Creating a ListView that loads one page at a time a community article by AbdulRahman AlHamali
-
Listview.builder
API
在 16ms 内渲染完成每一帧
Build and display frames in 16ms
由于构建和渲染有两个独立的线程,因此构建时间为 16ms,60Hz 显示器上渲染时间为 16ms。如果需要考虑延迟,就要在 16ms 或更短 的时间内构建和显示帧。请注意,这意味着构建需要少于 8ms,渲染也需要少于 8ms,总计 16ms 或更短。如果需要考虑丢帧(jankyness),那么每个构建和渲染阶段的 16ms 都可以。
Since there are two separate threads for building and rendering, you have 16ms for building, and 16ms for rendering on a 60Hz display. If latency is a concern, build and display a frame in 16ms or less. Note that means built in 8ms or less, and rendered in 8ms or less, for a total of 16ms or less. If missing frames (jankyness) is a concern, then 16ms for each of the build and render stages is OK.
如果在 profile 构建 状态下,每一帧渲染时间低于 16ms,你可能不必担心性能问题以及一些性能陷阱,但仍然应该致力于尽可能快地渲染每一帧。为什么?
If your frames are rendering in well under 16ms total in profile mode, you likely don’t have to worry about performance even if some performance pitfalls apply, but you should still aim to build and render a frame as fast as possible. Why?
-
将帧渲染时间降低到 16ms 以下可能在视觉上看不出来什么变化,但可以延长电池寿命以及避免发热问题。
Lowering the frame render time below 16ms might not make a visual difference, but it improves battery life and thermal issues.
-
可能在你当前测试设备上运行良好,但请考虑在应用所支持的最低端设备上的情况。
It might run fine on your device, but consider performance for the lowest device you are targeting.
-
当 120fps 的设备普及之后,便需要在 8ms 之内完成每一帧的渲染来保证流畅平滑的体验。
When 120fps devices become widely available, you’ll want to render frames in under 8ms (total) in order to provide the smoothest experience.
如果你想弄明白为什么 60fps 会带来平滑的视觉体验,请看视频 Why 60fps?
If you are wondering why 60fps leads to a smooth visual experience, see the video Why 60fps?
陷阱
Pitfalls
如果你需要改善应用程序的性能,或者 UI 流畅度没达到你的预期,那么 IDE 的 Flutter plugin 可以提供帮助。在 Flutter Performance 窗口中,勾选 Show widget rebuild information 复选框。此功能可帮助你检测帧的渲染和显示时间是否超过 16ms。在可能的情况下,插件也会提供指向相关提示的链接。
If you need to tune your app’s performance, or perhaps the UI isn’t as smooth as you expect, the Flutter plugin for your IDE can help. In the Flutter Performance window, enable the Show widget rebuild information check box. This feature helps you detect when frames are being rendered and displayed in more than 16ms. Where possible, the plugin provides a link to a relevant tip.
以下行为可能会对您应用的性能产生负面影响。
The following behaviors might negatively impact your app’s performance.
-
避免使用
Opacity
widget,尤其是在动画中避免使用。可以使用AnimatedOpacity
或FadeInImage
代替该操作。更多信息,请参阅 Performance considerations for opacity animation。Avoid using the
Opacity
widget, and particularly avoid it in an animation. UseAnimatedOpacity
orFadeInImage
instead. For more information, see Performance considerations for opacity animation. -
使用 AnimatedBuilder 时,请避免在不依赖于动画的 widget 的构造方法中构建 widget 树,不然,动画的每次变动都会重建这个 widget 树,应当将这部分子树作为 child 传递给 AnimatedBuilder,从而只构建一次。更多内容,请查看 这个文档。
When using an AnimatedBuilder, avoid putting a subtree in the builder function that builds widgets that don’t depend on the animation. This subtree is rebuilt for every tick of the animation. Instead, build that part of the subtree once and pass it as a child to the AnimatedBuilder. For more information, see Performance optimizations.
-
避免在动画中剪裁,尽可能的在动画开始之前预先剪裁图像。
Avoid clipping in an animation. If possible, pre-clip the image before animating it.
-
如果大多数 children widget 在屏幕上不可见,请避免使用返回具体列表的构造函数(例如
Column()
或ListView()
),以避免构建成本。Avoid using constructors with a concrete
List
of children (such asColumn()
orListView()
) if most of the children are not visible on screen to avoid the build cost.
参考资料
Resources
要了解更多性能信息,请参见以下资源:
For more performance info, see the following resources:
-
AnimatedBuilder API 页面的 Performance optimizations 部分;
Performance optimizations in the AnimatedBuilder API page
-
Opacity API 页面的 Performance considerations for opacity animation 部分;
Performance considerations for opacity animation in the Opacity API page
-
ListView API 页面中 Child elements’ lifecycle,以及如何高效加载元素;
Child elements’ lifecycle and how to load them efficiently, in the ListView API page
-
StatefulWidget
的 Performance considerations API 文档。Performance considerations of a
StatefulWidget