Flutter 性能分析

有人说“一个快速的应用程序很棒,但一个流畅的应用程序会更好”。如果您的应用程序没有平滑渲染,您该如何修复它?您从哪里开始?本指南向您展示从哪里开始、要采取哪些步骤以及可以提供帮助的工具。

诊断性能问题

要诊断出现性能问题的应用,您需要启用性能叠加层来查看 UI 和光栅线程。在开始之前,请确保您在配置文件模式下运行,并且您未使用模拟器。为了获得最佳结果,您可以选择用户可能使用的最慢设备。

连接到物理设备

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

在配置文件模式下运行

Flutter 的配置文件模式编译并启动您的应用程序几乎与发布模式相同,但仅具有足够的附加功能来允许调试性能问题。例如,配置文件模式向分析工具提供跟踪信息。

按照以下步骤在配置文件模式下启动应用

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

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

    $ flutter run --profile
    

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

首先,打开 DevTools 并查看性能叠加,如下一部分所述。

启动 DevTools

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

一旦您的应用程序在分析模式下运行,启动 DevTools

性能叠加

性能叠加在两个图表中显示统计信息,显示您的应用程序中花费时间的地方。如果 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 的线程

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 或光栅线程。此线程未在性能覆盖中显示。

有关更多信息和视频的链接,请参阅 GitHub wiki 上的 框架架构,以及社区文章 图层蛋糕

显示性能覆盖

您可以按如下方式切换显示性能覆盖

  • 使用 Flutter 检查器
  • 从命令行
  • 以编程方式

使用 Flutter 检查器

启用 PerformanceOverlay 小部件最简单的方法是使用 Flutter 检查器,它可在 DevTools 中的 检查器视图 中使用。只需单击性能覆盖按钮即可在正在运行的应用上切换覆盖。

从命令行

使用命令行中的P 键切换性能覆盖。

以编程方式

要以编程方式启用覆盖,请参阅 性能覆盖,这是 以编程方式调试 Flutter 应用 页面中的一个部分。

识别 UI 图形中的问题

如果性能覆盖在 UI 图表中显示为红色,即使 GPU 图表也显示为红色,也请先对 Dart VM 进行分析。

识别 GPU 图表中的问题

有时,场景会导致图层树易于构建,但渲染线程上的渲染成本很高。发生这种情况时,UI 图表没有红色,但 GPU 图表显示红色。在这种情况下,你需要找出导致渲染代码变慢的代码。对于 GPU 来说,特定类型的工作负载更难。它们可能涉及对 saveLayer 的不必要调用、多个对象的不透明度相交以及特定情况下的剪辑或阴影。

如果你怀疑缓慢的根源是在动画期间,请点击 Flutter 检查器中的缓慢动画按钮,将动画速度降低 5 倍。如果你想对速度有更多控制,也可以 以编程方式执行此操作。

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

检查离屏图层

saveLayer 方法是 Flutter 框架中最昂贵的方法之一。在对场景应用后期处理时很有用,但它会减慢你的应用速度,如果你不需要它,应该避免使用它。即使你没有显式调用 saveLayer,也可能代表你进行隐式调用。你可以使用 PerformanceOverlayLayer.checkerboardOffscreenLayers 开关检查你的场景是否正在使用 saveLayer

启用该开关后,运行应用并查找用闪烁框勾勒出的任何图像。如果正在渲染新帧,该框会逐帧闪烁。例如,你可能有一组使用 saveLayer 渲染的不透明度对象。在这种情况下,对每个单独的小组件应用不透明度可能比在小组件树中较高的父小组件中应用不透明度性能更高。对于其他可能昂贵的操作(例如剪辑或阴影)也是如此。

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

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

检查非缓存图像

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

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

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

您可以通过启用 PerformanceOverlayLayer.checkerboardRasterCacheImages 开关来查看正在缓存哪些图像。

运行该应用并查找使用随机彩色棋盘格渲染的图像,表明该图像已缓存。当您与场景交互时,棋盘格图像应保持不变——您不希望看到闪烁,这表明缓存的图像正在重新缓存。

在大多数情况下,您希望在静态图像上看到棋盘格,但不要在非静态图像上看到。如果静态图像未缓存,您可以将其放入 RepaintBoundary 小组件中以对其进行缓存。尽管如果引擎认为图像不够复杂,它仍然可能会忽略重绘边界。

查看小组件重建分析器

Flutter 框架旨在让创建不是 60fps 和流畅的应用程序变得困难。通常,如果您有 jank,那是因为有一个简单的错误导致每帧重建的 UI 比要求的更多。小组件重建分析器可帮助您调试和修复由于此类错误导致的性能问题。

您可以在 Android Studio 和 IntelliJ 的 Flutter 插件中查看当前屏幕和帧的小组件重建计数。有关如何执行此操作的详细信息,请参阅 显示性能数据

基准测试

您可以通过编写基准测试来衡量和跟踪应用的性能。Flutter Driver 库提供对基准测试的支持。使用此集成测试框架,您可以生成指标来跟踪以下内容

  • Jank
  • 下载大小
  • 电池效率
  • 启动时间

跟踪这些基准测试可让你在引入不利于性能的回归时获得通知。

有关更多信息,请查看集成测试

其他资源

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