跳到主内容

Flutter 性能分析

诊断 Flutter 中的 UI 性能问题。

概述

#

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

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

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

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

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

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

  • 小部件重建分析器(Android Studio 的 IntelliJ):卡顿通常是由不必要的 UI 重建引起的。如果您使用的是 Android Studio 的 IntelliJ,则小部件重建分析器可以通过显示当前屏幕和帧的小部件重建计数来帮助定位和修复这些问题。有关更多信息,请参阅 显示性能数据

Flutter 旨在提供 60 帧每秒 (fps) 的性能,或在支持该性能的设备上提供 120 fps。为了达到 60fps,每帧必须在大约 16 毫秒内渲染完毕,以避免卡顿。当帧渲染时间明显超过正常时间并被丢弃时,就会发生卡顿,从而导致动画出现可见的停顿。例如,如果某个帧偶尔需要比平时长 10 倍的时间才能渲染完毕,则它很可能会被丢弃,从而导致动画看起来很卡。

连接到真机设备

#

对于 Flutter 应用程序,几乎所有的性能调试都应在 Android 或 iOS 真机设备上进行,并且您的 Flutter 应用程序应以 Profile 模式 运行。使用调试模式,或在模拟器或仿真器上运行应用程序,通常不能代表发布模式构建的最终行为。您应该考虑检查用户可能合理使用的最慢设备上的性能。

以 Profile 模式运行

#

Flutter 的 Profile 模式以与发布模式几乎相同的方式编译和启动您的应用程序,但仅提供足够的功能来允许调试性能问题。例如,Profile 模式为分析工具提供跟踪信息。

按照以下步骤以 Profile 模式启动应用程序

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

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

  • 从命令行,使用 --profile 标志

    flutter run --profile
    

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

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

启动 DevTools

#

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

在您的应用程序以 Profile 模式运行时,启动 DevTools

显示性能叠加层

#

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

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

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

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

观察性能叠加层

#

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

本节介绍如何启用性能叠加层并使用它来诊断应用程序中卡顿的原因。以下屏幕截图显示了在 Flutter Gallery 示例上运行的性能叠加层

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

查看图表

#

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

性能叠加层应始终在 Profile 模式 中查看,因为 Debug 模式 性能会故意牺牲,以换取旨在帮助开发的昂贵的断言,因此结果具有误导性。

每个帧应在 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 图表中显示红色,请首先分析 Dart VM,即使 GPU 图表也显示红色。

查看 GPU 图表

#

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

如果你怀疑缓慢的原因是在动画过程中,请点击 Flutter inspector 中的 慢速动画 按钮,将动画速度降低 5 倍。如果你想要更多速度控制,也可以通过 编程方式 来实现。

缓慢是在第一帧发生,还是在整个动画过程中发生?如果是整个动画,剪裁是否导致了缓慢?也许有一种替代的绘制场景方式,不需要使用剪裁。例如,将不透明的角叠加到正方形上,而不是剪裁为圆角矩形。如果是一个正在淡入、旋转或以其他方式操作的静态场景,RepaintBoundary 可能会有所帮助。

检查越界层

#

saveLayer 方法是 Flutter 框架中最昂贵的方法之一。它在应用后期处理时很有用,但可能会降低你的应用程序速度,如果不需要它,应该避免使用。即使你没有显式调用 saveLayer,隐式调用也可能在你不知情的情况下发生,例如,当你指定 Clip.antiAliasWithSaveLayer(通常作为 clipBehavior)时。

例如,你可能有一组使用 saveLayer 渲染的具有透明度的对象。在这种情况下,将透明度应用于每个单独的 widget,而不是 widget 树中更高位置的父 widget,可能性能更高。对于其他潜在的昂贵操作,例如剪裁或阴影,也适用相同的原则。

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

  • 应用程序是否需要这种效果?
  • 是否可以消除其中任何调用?
  • 我能否将相同的效果应用于单个元素,而不是一个组?

检查未缓存的图像

#

使用 RepaintBoundary 缓存图像是好的,在有意义的情况下

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

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

其他资源

#

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