Flutter 性能分析
概览
#应用性能包括多个方面,从原生速度、I/O 吞吐量到用户界面的流畅性。虽然本篇主要关注用户界面的流畅性(无卡顿或抖动),但本篇介绍的工具通常也可用于诊断其他性能的问题。
Flutter 提供多种性能分析工具。以下是其中几种:
-
The Performance Overlay: Displays a simplified set of metrics directly within your running app. To learn more, see the sections in this topic.
-
The Performance View: A web-based interface that connects to your app and displays detailed performance metrics. Part of the DevTools utility. To learn more, see Use the Performance View.
-
Performance tracing within Dart: Add tracing directly into your app's Dart code, using the
dart:developer package
, and then track your app's performance in the DevTools utility. To learn more, see Tracing Dart code. -
Benchmarking: You can measure and track your app's performance by writing benchmark tests. The Flutter Driver library provides support for benchmarking. Using this integration test framework, you can generate metrics that track jank, download size, battery efficiency, and startup time. For more information, check out Integration testing.
-
Widget rebuild profiler (IntelliJ for Android Studio): Jank often arises from unnecessary UI rebuilds. If you are using IntelliJ for Android Studio, the Widget Rebuild Profiler helps pinpoint and fix these issues by showing widget rebuild counts for the current screen and frame. For more information, see Show performance data.
Flutter 的目标是提供 60 帧每秒 (fps) 的性能,或者是在可以达到 120 Hz 的设备上提供 120 fps 的性能。对于 60 fps 来说,为了避免卡顿(抖动),需要在约每 16 ms 的时候渲染一帧。当帧的呈现时间明显延长并被丢弃时,卡顿就随之产生了,动画会出现明显的停顿。举例来说,如果一帧花了 10 倍的时间来渲染,这帧就可能会被丢弃,动画看起来就会卡顿。
连接到真机设备
#几乎全部的 Flutter 应用性能调试都应该在真实的 Android 或者 iOS 设备上以 分析模式 进行。通常来说,调试模式或者是模拟器上运行的应用的性能指标和发布模式的表现并不相同。 应该考虑在用户使用的最慢的设备上检查性能。
在 Profile 分析模式运行
#除了一些调试性能问题所必须的额外方法, Flutter 的分析模式和发布模式的编译和运行基本相同。例如,分析模式为分析工具提供了追踪信息。
使用分析模式运行应用的方法:
-
在 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
#DevTool 提供诸如性能分析、堆测试以及显示代码覆盖率等功能。 DevTool 的 Timeline view 界面可以让开发者逐帧分析应用的 UI 性能。
一旦你的应用程序在 Profile 模式下运行,即 运行 DevTools。
Display the performance overlay
#You can toggle the display of the performance overlay as follows:
-
DevTools Performance view: The easiest way to enable the PerformanceOverlay widget is from the Performance view in DevTools. Simply click the Performance Overlay button to toggle the overlay on your running app.
-
command line: Toggle the performance overlay using the P key from the command line.
-
programmatically: To enable the overlay programmatically, see Performance overlay, a section in the Debugging Flutter apps programmatically page.
观察性能图层
#性能图层用两张图表显示应用的耗时信息。如果 UI 产生了卡顿(跳帧),这些图表可以帮助分析原因。图表在当前应用的最上层展示,但并不是用普通的 widget 方式绘制的—Flutter 引擎自身绘制了该图层来尽可能减少对性能的影响。每一张图表都代表当前线程的最近 300 帧表现。
本节阐述如何打开性能图层并用其来分析应用中卡顿的原因。下面的截图展示了 Flutter Gallery 样例的性能图层:
显示 Raster 线程(上面)和 UI 线程(下面)的性能叠加图。
垂直的绿色条条代表的是当前帧。
审查图表
#最顶部(标志了 “GPU”)的图形表示 raster 线程所花费的时间,底部的图表显示了 UI 线程所花费的时间。横跨图表中的白线代表了 16 ms 内沿竖轴的增量;如果这些线在图表中都没有超过它的话,说明你的运行帧率低于 60 Hz。而横轴则表示帧。只有当你的应用绘制时这个图表才会更新,所以如果它空闲的话,图表就不会动。
这个浮层只应在 分析模式 中使用,因为在 调试模式 下有意牺牲了性能来换取昂贵的断言以帮助开发,所以这时候的结果会有误导性。
每一帧都应该在 1/60 秒(大约 16 ms)内创建并显示。如果有一帧超时(任意图像)而无法显示,就导致了卡顿,图表之一就会展示出来一个红色竖条。如果是在 UI 图表出现了红色竖条,则表明 Dart 代码消耗了大量资源。而如果红色竖条是在 GPU 图表出现的,意味着场景太复杂导致无法快速渲染。
红色竖条表明当前帧的渲染和绘制都很耗时。
当两张图表都是红色时,就要开始对 UI 线程 (Dart VM) 进行诊断了。
审查线程
#Flutter 使用多个线程来完成其必要的工作,图层中仅展示了其中两个线程。你写的所有 Dart 代码都在 UI 线程上运行。尽管你没有直接访问其他线程的权限,但是你对 UI 线程的操作会对其他线程产生性能影响。
平台线程
平台的主线程。插件代码在此运行。更多信息,请查阅 iOS 的 UIKit 文档,或者 Android 的 MainThread 文档。
该线程将不会显示在 performance overlay 上
UI 线程
UI 线程在 Dart VM 中执行 Dart 代码。该线程包括开发者写下的代码和 Flutter 框架根据应用行为生成的代码。当应用创建和展示场景的时候,UI 线程首先建立一个 图层树(layer tree) ,一个包含设备无关的渲染命令的轻量对象,并将图层树发送到 GPU 线程来渲染到设备上。
不要阻塞这个线程! 在性能图层的最低栏展示该线程。
Raster 线程
raster 线程拿到 layer tree,并将它交给 GPU(图形处理单元)。你无法直接与 GPU 线程或其数据通信,但如果该线程变慢,一定是开发者 Dart 代码中的某处导致的。图形库 Skia 在该线程运行,并在性能图层的最顶栏显示该线程。请注意,raster 线程为 GPU 进行栅格化,而线程本身则是在 CPU 上运行的。
I/O线程
执行昂贵的操作(常见的有 I/O)以避免阻塞 UI 或者 raster 线程。
该线程将不会显示在 performance overlay 上。
你可以在 Flutter wiki 上的框架结构 (The Framework architecture) 一文中了解更多信息和一些视频内容,另外你可以在我们的社区中查看文章 The Layer Cake。
定位问题
#审查 UI 图表
#如果性能图层的 UI 图表显示红色,就要从分析 Dart VM 开始着手了,即使 GPU 图表同样显示红色。
审查 GPU 图表
#有些情况下界面的图层树构造起来虽然容易,但在 raster 线程下渲染却很耗时。这种情况发生时,UI 图表没有红色,但 GPU 图表会显示红色。这时需要找出代码中导致渲染缓慢的原因。特定类型的负载对 GPU 来说会更加复杂。可能包括不必要的对 saveLayer
的调用,许多对象间的复杂操作,还可能是特定情形下的裁剪或者阴影。
如果推断的原因是动画中的卡顿的话,可以点击 Flutter inspector 中的 Slow Animations 按钮,来使动画速度减慢 5 倍。如果你想从更多方面控制动画速度,你可以参考 programmatically。
卡顿是第一帧发生的还是贯穿整个动画过程呢?如果是整个动画过程的话,会是裁剪导致的吗?也许有可以替代裁剪的方法来绘制场景。比如说,不透明图层的长方形中用尖角来取代圆角裁剪。如果是一个静态场景的淡入、旋转或者其他操作,可以尝试使用重绘边界 (RepaintBoundary
)。
检查屏幕之外的视图
#saveLayer
方法是 Flutter 框架中特别消耗性能的操作之一。更新屏幕时这个方法很有用,但它可能使应用变慢,如果不是必须的话,应尽量避免使用这个方法。即便你自己没有明确地调用 saveLayer
,也可能在其他操作中间接调用了该方法,例如在指定 Clip.antiAliasWithSaveLayer
(通常用于 clipBehavior
)时就会调用。
举个例子,也许有一组对象的透明度要使用 saveLayer
来渲染。在这种情况下,相比通过 widget 树中高层次的父 widget 操作,单独对每个 widget 来应用透明度可能性能会更好。其他可能大量消耗资源的操作也同理,比如裁剪或者阴影。
当遇到对 saveLayer
的调用时,先问问自己:
-
应用是否需要这个效果?
-
可以减少调用么?
-
可以对单独元素操作而不是一组元素么?
检查没有缓存的图像
#使用重绘边界 (RepaintBoundary
) 来缓存图片是个好主意,当需要的时候。
从资源的角度看,特别消耗性能的操作之一是用图像文件来渲染纹理。首先,需要从持久存储中取出压缩图像,然后解压缩到宿主存储中(GPU 存储),再传输到设备存储器中 (RAM)。
也就是说,图像的 I/O 操作是特别消耗性能的。缓存提供了复杂层次的快照,这样就可以方便地渲染到随后的帧中。 因为光栅缓存入口的构建需要大量资源,同时增加了 GPU 存储的负载,所以只在必须时才缓存图片。
更多资源
#以下链接提供了关于 Flutter 工具的使用和 Flutter 调试的更多信息:
-
Flutter Inspector talk, 一个在 DartConf 2018 大会的演讲
-
Hackernoon 专栏的一篇文章 为什么 Flutter 使用 Dart
-
Flutter YouTube 频道的一个视频:为什么 Flutter 使用 Dart 的
-
Dart 开发者工具: Dart 和 Flutter 应用的开发者性能调试工具;
-
Flutter API 文档, 特别是
PerformanceOverlay
这个类和 dart:developer 这个 package。
除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2025-02-13。 查看文档源码 或者 为本页面内容提出建议。