概述

#

应用性能涵盖了从原始速度、I/O 吞吐量到用户界面流畅度等多个方面。本页面主要关注 UI 流畅度(避免卡顿或掉帧),但此处描述的工具也常用于诊断其他性能问题。

Flutter 提供了几种性能分析工具。以下是其中一些:

  • 性能叠加层:直接在正在运行的应用中显示一组简化的指标。要了解更多信息,请参阅本主题中的相关章节。

  • 性能视图:一个基于 Web 的界面,可连接到您的应用并显示详细的性能指标。它是 DevTools 工具的一部分。要了解更多信息,请参阅使用性能视图

  • Dart 内部的性能追踪:使用 dart:developer package 直接在您的应用 Dart 代码中添加追踪,然后在 DevTools 工具中追踪应用的性能。要了解更多信息,请参阅追踪 Dart 代码

  • 基准测试:您可以通过编写基准测试来衡量和追踪应用的性能。Flutter Driver 库提供了对基准测试的支持。使用这个集成测试框架,您可以生成追踪掉帧、下载大小、电池效率和启动时间的指标。有关更多信息,请查看集成测试

  • 组件重建分析器(IntelliJ for Android Studio):掉帧通常是由于不必要的 UI 重建引起的。如果您使用 IntelliJ for Android Studio,组件重建分析器通过显示当前屏幕和帧的组件重建计数,帮助您查明并修复这些问题。有关更多信息,请参阅显示性能数据

Flutter 的目标是提供每秒 60 帧(fps)的性能,或者在支持的设备上达到 120 fps。为了实现 60fps,每个帧必须大约每 16 毫秒渲染一次,以避免掉帧。掉帧发生在帧渲染时间显著过长并被丢弃时,导致动画出现明显的卡顿。例如,如果一个帧偶尔需要比平时多 10 倍的时间来渲染,它很可能会被丢弃,导致动画看起来不连贯。

连接到物理设备

#

几乎所有 Flutter 应用的性能调试都应在物理 Android 或 iOS 设备上进行,并且您的 Flutter 应用应在配置文件模式下运行。使用调试模式,或在模拟器或仿真器上运行应用,通常不能反映发布模式构建的最终行为。您应该考虑在用户可能合理使用的最慢设备上检查性能。

在配置文件模式下运行

#

Flutter 的配置文件模式编译和启动您的应用的方式与发布模式几乎相同,但增加了足够的功能以允许调试性能问题。例如,配置文件模式会向分析工具提供追踪信息。

以下是在配置文件模式下启动应用的方法

  • 在 VS Code 中,打开您的 launch.json 文件,并将 flutterMode 属性设置为 profile(完成分析后,将其改回 releasedebug

    json
    "configurations": [
      {
        "name": "Flutter",
        "request": "launch",
        "type": "dart",
        "flutterMode": "profile"
      }
    ]
  • 在 Android Studio 和 IntelliJ 中,使用运行 > Flutter Run main.dart in Profile Mode 菜单项。

  • 在命令行中,使用 --profile 标志

    flutter run --profile

有关不同模式的更多信息,请参阅Flutter 的构建模式

您将首先打开 DevTools 并查看性能叠加层,如下一节所述。

启动 DevTools

#

DevTools 提供分析、检查堆、显示代码覆盖率、启用性能叠加层和逐步调试器等功能。DevTools 的时间线视图允许您逐帧调查应用的 UI 性能。

当您的应用在配置文件模式下运行后,启动 DevTools

显示性能叠加层

#

您可以通过以下方式切换性能叠加层的显示

  • DevTools 性能视图:启用 PerformanceOverlay 组件最简单的方法是使用 DevTools 中的性能视图。只需点击性能叠加层按钮即可在您正在运行的应用上切换叠加层。

  • 命令行:从命令行使用 P 键切换性能叠加层。

  • 编程方式:要以编程方式启用叠加层,请参阅以编程方式调试 Flutter 应用页面中的性能叠加层一节。

观察性能叠加层

#

性能叠加层在两个图表中显示统计数据,这些图表显示了您的应用在何处花费时间。如果 UI 出现卡顿(跳帧),这些图表可以帮助您找出原因。这些图表显示在您正在运行的应用之上,但它们不像普通组件那样绘制——Flutter 引擎本身绘制叠加层,并且对性能的影响微乎其微。每个图表代表该线程的最后 300 帧。

本节描述了如何启用性能叠加层并使用它来诊断应用中掉帧的原因。以下截图显示了在 Flutter Gallery 示例上运行的性能叠加层

Screenshot of overlay showing zero jank
性能叠加层显示了光栅线程(顶部)和 UI 线程(底部)。
垂直的绿色条表示当前帧。

查看图表

#

顶部图表(标记为“GPU”)显示光栅线程所花费的时间,底部图表显示 UI 线程所花费的时间。图表上的白色线条沿垂直轴显示 16 毫秒的增量;如果图表超出其中任何一条线,则表示您运行的帧率低于 60Hz。水平轴表示帧。图表仅在您的应用绘制时更新,因此如果应用处于空闲状态,图表将停止移动。

叠加层应始终在配置文件模式下查看,因为调试模式的性能是故意牺牲的,以换取旨在帮助开发的昂贵断言,因此结果具有误导性。

每个帧应在 1/60 秒(大约 16 毫秒)内创建和显示。如果帧超出此限制(在任一图表中),则无法显示,从而导致掉帧,并且图表中的一个或两个都会出现垂直红条。如果 UI 图表中出现红条,则表示 Dart 代码的开销过高。如果 GPU 图表中出现垂直红条,则表示场景过于复杂,无法快速渲染。

Screenshot of performance overlay showing jank with red bars
垂直红条表示当前帧在渲染和绘制方面开销都很大。
当两个图表都显示红色时,请从诊断 UI 线程开始。

查看线程

#

Flutter 使用多个线程来完成其工作,尽管叠加层中只显示了其中两个线程。您所有的 Dart 代码都在 UI 线程上运行。尽管您无法直接访问任何其他线程,但您在 UI 线程上的操作会对其他线程产生性能影响。

平台线程
平台的主线程。插件代码在此处运行。有关更多信息,请参阅 iOS 的 UIKit 文档,或 Android 的 MainThread 文档。此线程未在性能叠加层中显示。
UI 线程
UI 线程在 Dart VM 中执行 Dart 代码。此线程包括您编写的代码以及 Flutter 框架代表您的应用执行的代码。当您的应用创建并显示场景时,UI 线程会创建一个图层树,这是一个包含设备无关绘制命令的轻量级对象,并将该图层树发送到光栅线程以在设备上渲染。不要阻塞此线程!显示在性能叠加层的底部一行。
光栅线程
光栅线程接收图层树并通过与 GPU(图形处理单元)通信来显示它。您无法直接访问光栅线程或其数据,但是,如果此线程很慢,则是因为您在 Dart 代码中执行了某些操作。图形库 Skia 和 Impeller 在此线程上运行。显示在性能叠加层的顶部一行。请注意,虽然光栅线程为 GPU 进行光栅化,但线程本身在 CPU 上运行。
I/O 线程
执行开销较大的任务(主要是 I/O),否则会阻塞 UI 或光栅线程。此线程未在性能叠加层中显示。

有关更多信息和视频的链接,请参阅Flutter Wiki 中的 框架架构,以及社区文章分层蛋糕

识别问题

#

查看 UI 图表

#

如果性能叠加层在 UI 图表中显示红色,即使 GPU 图表也显示红色,也要从分析 Dart VM 开始。

查看 GPU 图表

#

有时,一个场景会生成一个易于构建但在光栅线程上渲染开销很大的图层树。发生这种情况时,UI 图表没有红色,但 GPU 图表显示红色。在这种情况下,您需要弄清楚您的代码在做什么导致渲染代码变慢。某些类型的工作负载对 GPU 来说更困难。它们可能涉及不必要的 saveLayer 调用、多个对象交叉的不透明度,以及特定情况下的剪裁或阴影。

如果您怀疑慢速是发生在动画期间,请点击 Flutter inspector 中的慢动画按钮,将动画速度降低 5 倍。如果您想要更精细的速度控制,也可以通过编程方式实现。

慢速是发生在第一帧,还是发生在整个动画中?如果是整个动画,是否是剪裁导致了速度变慢?也许有另一种不使用剪裁来绘制场景的方法。例如,在正方形上覆盖不透明的角,而不是剪裁成圆角矩形。如果是一个正在进行淡入、旋转或以其他方式操作的静态场景,RepaintBoundary 可能会有所帮助。

检查屏幕外图层

#

saveLayer 方法是 Flutter 框架中最耗费性能的方法之一。它在对场景应用后处理时很有用,但它可能会使您的应用变慢,如果不需要应避免使用。即使您没有显式调用 saveLayer,也可能会代表您发生隐式调用,例如在指定 Clip.antiAliasWithSaveLayer(通常作为 clipBehavior)时。

例如,您可能有一组使用 saveLayer 渲染的具有不透明度的对象。在这种情况下,最好是对每个单独的组件应用不透明度,而不是对组件树中更高层的父组件应用,这样性能可能会更好。其他可能开销很大的操作,如剪裁或阴影,也是如此。

当您遇到 saveLayer 调用时,请问自己以下问题

  • 应用需要此效果吗?
  • 这些调用中是否有任何可以消除的?
  • 我可以将相同的效果应用于单个元素而不是组吗?

检查未缓存的图像

#

使用 RepaintBoundary 缓存图像是好的,但仅限于有意义时

从资源角度来看,最耗费性能的操作之一是使用图像文件渲染纹理。首先,从持久存储中获取压缩图像。图像被解压缩到主机内存(GPU 内存),并传输到设备内存(RAM)。

换句话说,图像 I/O 可能开销很大。缓存提供了复杂层级结构的快照,以便在后续帧中更容易渲染。由于光栅缓存条目构建成本高昂且占用大量 GPU 内存,因此仅在绝对必要时才缓存图像。

其他资源

#

以下资源提供了有关在 Flutter 中使用其工具和调试的更多信息