Flutter 性能分析
概述
#应用性能包含多个方面,从原始速度和 I/O 吞吐量到用户界面的流畅度。虽然本页主要关注 UI 流畅度(无卡顿或掉帧),但这里介绍的工具通常也可用于诊断其他性能问题。
Flutter 提供了几种性能分析工具。以下是其中一些:
性能叠加层:直接在运行的应用中显示一套简化的指标。要了解更多信息,请参阅本主题中的相关章节。
性能视图:一个基于 Web 的界面,可连接到您的应用并显示详细的性能指标。这是 DevTools 工具的一部分。要了解更多信息,请参阅使用性能视图。
Dart 中的性能跟踪:使用
dart:developer
包直接在应用的 Dart 代码中添加跟踪,然后在 DevTools 工具中跟踪应用的性能。要了解更多信息,请参阅跟踪 Dart 代码。基准测试:您可以通过编写基准测试来衡量和跟踪应用的性能。Flutter Driver 库提供了对基准测试的支持。使用此集成测试框架,您可以生成跟踪掉帧、下载大小、电池效率和启动时间的指标。有关更多信息,请查看集成测试。
Widget 重建剖析器 (IntelliJ/Android Studio):掉帧通常源于不必要的 UI 重建。如果您使用的是 IntelliJ/Android Studio,Widget 重建剖析器通过显示当前屏幕和帧的 Widget 重建计数来帮助定位和修复这些问题。有关更多信息,请参阅显示性能数据。
Flutter 的目标是提供每秒 60 帧 (fps) 的性能,或在支持的设备上提供 120 fps。为了达到 60 fps,每帧必须在大约 16 毫秒内渲染完成,以避免掉帧。当帧渲染时间显著增加并被丢弃时,就会发生掉帧,导致动画出现明显的卡顿。例如,如果一帧的渲染时间偶尔是平常的 10 倍,那么它很可能会被丢弃,导致动画看起来卡顿。
连接到物理设备
#几乎所有 Flutter 应用的性能调试都应在物理 Android 或 iOS 设备上进行,并且您的 Flutter 应用应以profile 模式运行。使用 debug 模式或在模拟器/仿真器上运行应用,通常不能反映 release 模式构建的最终行为。您应该考虑在用户可能合理使用的最慢的设备上检查性能。
以 profile 模式运行
#Flutter 的 profile 模式以几乎与 release 模式相同的方式编译和启动您的应用程序,但增加了足够多的额外功能以允许调试性能问题。例如,profile 模式会向性能分析工具提供跟踪信息。
按以下方式以 profile 模式启动应用
在 VS Code 中,打开您的
launch.json
文件,并将flutterMode
属性设置为profile
(完成性能分析后,将其改回release
或debug
)。json"configurations": [ { "name": "Flutter", "request": "launch", "type": "dart", "flutterMode": "profile" } ]
在 Android Studio 和 IntelliJ 中,使用 **Run > Flutter Run main.dart in Profile Mode** 菜单项。
从命令行,使用
--profile
标志。flutter run --profile
有关不同模式的更多信息,请参阅Flutter 的构建模式。
您将首先打开 DevTools 并查看性能叠加层,如下一节所述。
启动 DevTools
#DevTools 提供性能分析、检查堆、显示代码覆盖率、启用性能叠加层和逐步调试器等功能。DevTools 的时间线视图允许您逐帧查看应用的 UI 性能。
一旦您的应用以 profile 模式运行,就启动 DevTools。
显示性能叠加层
#您可以按以下方式切换性能叠加层的显示
DevTools 性能视图:从 DevTools 中的 性能视图启用 PerformanceOverlay widget 的最简单方法。只需点击 **Performance Overlay** 按钮即可在运行的应用上切换叠加层。
命令行:从命令行使用 **P** 键切换性能叠加层。
以编程方式:要以编程方式启用叠加层,请参阅性能叠加层,这是以编程方式调试 Flutter 应用页面中的一个部分。
观察性能叠加层
#性能叠加层在两个图表中显示统计信息,这些图表显示了您的应用中花费时间的位置。如果 UI 出现卡顿(丢帧),这些图表可以帮助您找出原因。图表显示在运行的应用之上,但它们的绘制方式不同于普通 widget——Flutter 引擎本身绘制叠加层,并且对性能的影响最小。每个图表代表该线程的最后 300 帧。
本节介绍如何启用性能叠加层并使用它来诊断应用中掉帧的原因。下图显示了在 Flutter Gallery 示例上运行的性能叠加层。
性能叠加层显示光栅线程(顶部)和 UI 线程(底部)。
垂直绿条代表当前帧。
检查图表
#顶部图表(标记为“GPU”)显示光栅线程花费的时间,底部图表显示 UI 线程花费的时间。图表上的水平白线表示垂直轴上的 16 毫秒增量;如果图表超出其中一条线,则表示运行频率低于 60Hz。水平轴表示帧。该图表仅在您的应用程序绘制时更新,因此如果它处于空闲状态,图表就会停止移动。
叠加层应始终在 profile 模式下查看,因为 debug 模式的性能是为了换取昂贵的断言而故意牺牲的,这些断言旨在辅助开发,因此结果具有误导性。
每一帧都应在 1/60 秒(约 16 毫秒)内创建并显示。超出此限制的帧(任一图表)将无法显示,导致掉帧,并且会在一个或两个图表中出现垂直红条。如果 UI 图表中出现红条,则表示 Dart 代码过于耗费资源。如果 GPU 图表中出现垂直红条,则表示场景过于复杂,无法快速渲染。
垂直红条表示当前帧渲染和绘制都很耗时。
当两个图表都显示红色时,请先诊断 UI 线程。
检查线程
#Flutter 使用多个线程来完成工作,尽管叠加层中只显示了两个线程。您所有的 Dart 代码都在 UI 线程上运行。尽管您无法直接访问任何其他线程,但您在 UI 线程上的操作会对其他线程产生性能影响。
- 平台线程
- 平台的主线程。插件代码在此处运行。有关更多信息,请参阅 iOS 的UIKit文档,或 Android 的MainThread文档。此线程未在性能叠加层中显示。
- UI 线程
- UI 线程在 Dart VM 中执行 Dart 代码。此线程包括您编写的代码以及 Flutter 框架代表您的应用程序执行的代码。当您的应用创建并显示一个场景时,UI 线程会创建一个“层树”(layer tree),这是一个轻量级的对象,包含与设备无关的绘制命令,然后将层树发送到光栅线程以在设备上渲染。不要阻塞此线程!显示在性能叠加层的底部行。
- 光栅线程
- 光栅线程负责处理层树并通过与 GPU(图形处理单元)交互来显示它。您无法直接访问光栅线程或其数据,但如果此线程变慢,则是由您在 Dart 代码中所做的事情引起的。Skia 和 Impeller(图形库)在此线程上运行。显示在性能叠加层的顶部行。请注意,尽管光栅线程为 GPU 进行光栅化,但线程本身在 CPU 上运行。
- I/O 线程
- 执行耗时任务(主要是 I/O),否则这些任务会阻塞 UI 或光栅线程。此线程未在性能叠加层中显示。
有关更多信息和视频的链接,请参阅Flutter Wiki中的框架架构,以及社区文章The Layer Cake。
识别问题
#检查 UI 图表
#如果性能叠加层在 UI 图表中显示红色,请先分析 Dart VM,即使 GPU 图表也显示红色。
检查 GPU 图表
#有时一个场景会生成一个易于构建但光栅线程渲染起来很耗时的层树。在这种情况下,UI 图表没有红色,但 GPU 图表显示红色。在这种情况下,您需要找出您的代码在做什么导致渲染代码变慢。特定类型的工作负载对 GPU 来说更具挑战性。它们可能涉及不必要的对 saveLayer
的调用、与多个对象的交叉透明度以及在特定情况下的剪裁或阴影。
如果您怀疑缓慢的来源在动画期间,请点击 Flutter Inspector 中的 **Slow Animations** 按钮将动画速度减慢 5 倍。如果您想更精确地控制速度,也可以 以编程方式完成。
是第一个帧缓慢,还是整个动画都缓慢?如果是整个动画,是剪裁导致了减速吗?也许有另一种绘制场景的方式而不使用剪裁。例如,将不透明的角叠加到正方形上,而不是剪裁成圆角矩形。如果是一个静态场景正在进行淡入、旋转或其他操作,RepaintBoundary
可能会有帮助。
检查屏幕外图层
#saveLayer
方法是 Flutter 框架中最耗费资源的方法之一。它在对场景应用后处理时很有用,但它会降低应用的性能,如果不需要,应避免使用。即使您不显式调用 saveLayer
,也可能会代表您隐式调用,例如在指定 Clip.antiAliasWithSaveLayer
(通常作为 clipBehavior
)时。
例如,您可能有一组使用 saveLayer
渲染的具有透明度的对象。在这种情况下,将透明度应用于每个单独的 widget 可能比应用于 widget 树中更高的父 widget 更有效。对于其他可能耗费资源的操作,如剪裁或阴影,也是如此。
当遇到对 saveLayer
的调用时,问自己这些问题:
- 应用是否需要此效果?
- 是否可以消除其中任何一次调用?
- 我能否将相同的效果应用于单个元素而不是一个组?
检查未缓存的图像
#使用 RepaintBoundary
缓存图像是好的,当它有意义的时候。
从资源角度来看,最耗费资源的操作之一是使用图像文件渲染纹理。首先,压缩图像从持久存储中获取。图像被解压缩到主机内存(GPU 内存),然后传输到设备内存(RAM)。
换句话说,图像 I/O 可能很耗费资源。缓存提供了复杂层级的快照,以便在后续帧中更容易渲染。由于光栅缓存条目构建成本高昂且占用大量 GPU 内存,因此仅在绝对必要时才缓存图像。
其他资源
#以下资源提供了有关使用 Flutter 工具和在 Flutter 中进行调试的更多信息:
- 调试
- 性能视图
- Flutter 检查器
- Flutter Inspector 讲座,在 DartConf 2018 上发表
- 为什么 Flutter 使用 Dart,Hackernoon 上的一篇文章
- 为什么 Flutter 使用 Dart,Flutter 频道上的一个视频
- DevTools:Dart 和 Flutter 应用的性能工具
- Flutter API 文档,特别是
PerformanceOverlay
类,以及dart:developer 包