Flutter 性能分析

有句话叫“的应用固然很好,但流畅的应用则更好。”如果你的应用渲染并不流畅,该怎么处理呢?从哪里着手呢?本文展示了应该从哪里着手,步骤以及可以提供帮助的工具。

It’s been said that “a fast app is great, but a smooth app is even better.” If your app isn’t rendering smoothly, how do you fix it? Where do you begin? This guide shows you where to start, steps to take, and tools that can help.

分析性能问题

Diagnosing performance problems

分析应用的性能问题需要打开性能监控图层(performance overlay)来观察 UI 和 GPU 线程。在此之前,要确保是在分析模式(profile mode)下运行,而且当前设备不是虚拟机。使用用户可能采用的最慢设备来获取最佳结果。

To diagnose an app with performance problems, you’ll enable the performance overlay to look at the UI and GPU threads. Before you begin, you want to make sure that you’re running in profile mode, and that you’re not using an emulator. For best results, you might choose the slowest device that your users might use.

连接到物理设备

Connect to a physical device

几乎全部的 Flutter 应用性能调试都应该在真实的 Android 或者 iOS 设备上以分析模式进行。通常来说,调试模式或者是模拟器上运行的应用的性能指标和发布模式的表现并不相同。 应该考虑在用户使用的最慢的设备上检查性能。

Almost all performance debugging for Flutter applications should be conducted on a physical Android or iOS device, with your Flutter application running in profile mode. Using debug mode, or running apps on simulators or emulators, is generally not indicative of the final behavior of release mode builds. You should consider checking performance on the slowest device that your users might reasonably use.

在分析模式运行

Run in profile mode

除了一些调试性能问题所必须的额外方法,Flutter 的分析模式和发布模式的编译和运行基本相同。例如,分析模式为分析工具提供了追踪信息。

Flutter’s profile mode compiles and launches your application almost identically to release mode, but with just enough additional functionality to allow debugging performance problems. For example, profile mode provides tracing information to profiling tools.

使用分析模式运行应用的方法:

Launch the app in profile mode as follows:

  • 在 Android Studio 和 IntelliJ 使用 Run > Flutter Run main.dart in Profile Mode 选项

    In Android Studio and IntelliJ, use the Run > Flutter Run main.dart in Profile Mode menu item.

  • 在 VS Code中,打开 launch.json 文件,设置 flutterMode 属性为 profile(当分析完成后,改回 release 或者 debug):

    In VS Code, open your launch.json file, and set the flutterMode property to profile (when done profiling, change it back to release or debug):

    "configurations": [
      {
        "name": "Flutter",
        "request": "launch",
        "type": "dart",
        "flutterMode": "profile"
      }
    ]
    
  • From the command line, use the --profile flag: 命令行使用 --profile 参数运行

    $ flutter run --profile
    

关于不同模式的更多信息,请参考 Flutter 的构建模式选择

For more information on how the different modes work, see Flutter’s build modes.

下面我们会从开启性能图层开始讲述

You’ll begin by enabling the performance overlay, as discussed in the next section.

性能图层

The performance overlay

性能图层用两张图表显示应用的耗时信息。如果 UI 产生了卡顿(跳帧),这些图表可以帮助分析原因。图表在当前应用的最上层展示,但并不是用普通的 widget 方式绘制的—Flutter 引擎自身绘制了该图层来尽可能减少对性能的影响。每一张图表都代表当前线程的最近 300 帧表现。

The performance overlay displays statistics in two graphs that show where time is being spent in your app. If the UI is janky (skipping frames), these graphs help you figure out why. The graphs display on top of your running app, but they aren’t drawn like a normal widget—the Flutter engine itself paints the overlay and only minimally impacts performance. Each graph represents the last 300 frames for that thread.

本节阐述如何打开 性能图层 并用其来分析应用中卡顿的原因。下面的截图展示了 Flutter Gallery 样例的性能图层:

This section describes how to enable the PerformanceOverlay, and use it to diagnose the cause of jank in your application. The following screenshot shows the performance overlay running on the Flutter Gallery example:

screenshot of performance overlay showing zero jank
GPU 线程的性能情况在上面,UI 线程显示在下面,垂直的绿色条条代表的是当前帧。


Flutter 用了一些额外的线程来完成这项工作。开发者的 Dart 代码都在 UI 线程运行。尽管没有直接访问其他线程的权限,但 UI 线程的动作还是对其他线程的性能有影响的。

Flutter uses several threads to do its work. All your Dart code runs on the UI thread. Although you have no direct access to any other thread, your actions on the UI thread have performance consequences on other threads.

  1. 平台线程
    该平台的主线程。插件代码在这里运行。更多信息请参阅:iOS 的 UIKit 文档,或者 Android 的 MainThread 文档。性能图层并不会展示该线程。

    Platform thread
    The platform’s main thread. Plugin code runs here. For more information, see the UIKit documentation for iOS, or the MainThread documentation for Android. This thread is not shown in the performance overlay.

  2. UI 线程
    UI 线程在 Dart VM 执行 Dart 代码。该线程包括开发者写下的代码和 Flutter 框架根据应用行为生成的代码。当应用创建和展示场景的时候,UI 线程首先建立一个 图层树(layer tree) ,一个包含设备无关的渲染命令的轻量对象,并将图层树发送到 GPU 线程来渲染到设备上。 不要阻塞这个线程! 在性能图层的最低栏展示该线程。

    UI thread
    The UI thread executes Dart code in the Dart VM. This thread includes code that you wrote, and code executed by Flutter’s framework on your app’s behalf. When your app creates and displays a scene, the UI thread creates a layer tree, a lightweight object containing device-agnostic painting commands, and sends the layer tree to the GPU thread to be rendered on the device. Don’t block this thread! Shown in the bottom row of the performance overlay.

  3. GPU 线程
    GPU 线程取回图层树并通知 GPU 渲染。尽管无法直接与 GPU 线程或其数据通信,但如果该线程变慢,一定是开发者 Dart 代码中的某处导致的。图形库 Skia 在该线程运行,有时也被叫做 光栅器(rasterizer)线程 。在性能图层的最顶栏展示该线程。

    GPU thread
    The GPU thread takes the layer tree and displays it by talking to the GPU (graphic processing unit). You cannot directly access the GPU thread or its data but, if this thread is slow, it’s a result of something you’ve done in the Dart code. Skia, the graphics library, runs on this thread, which is sometimes called the rasterizer thread. Shown in the top row of the performance overlay.

  4. I/O 线程
    可能阻塞 UI 或者 GPU 线程的耗时任务(大多数情况下是I/O)。该线程并不会在性能图层中展示。

    I/O thread
    Performs expensive tasks (mostly I/O) that would otherwise block either the UI or GPU threads. This thread is not shown in the performance overlay.

更多关于这些线程的信息请参阅 Architecture notes

For more information on these threads, see Architecture notes.

每一帧都应该在 1/60 秒(大约 16ms)内创建并显示。如果有一帧超时(任意图像)而无法显示,就导致了卡顿,图表之一就会展示出来一个红色竖条。如果是在 UI 图表出现了红色竖条,则表明 Dart 代码消耗了大量资源。而如果红色竖条是在 GPU 图表出现的,意味着场景太复杂导致无法快速渲染。

Each frame should be created and displayed within 1/60th of a second (approximately 16ms). A frame exceeding this limit (in either graph) fails to display, resulting in jank, and a vertical red bar appears in one or both of the graphs. If a red bar appears in the UI graph, the Dart code is too expensive. If a red vertical bar appears in the GPU graph, the scene is too complicated to render quickly.

Screenshot of performance overlay showing jank with red bars.
红色竖条表明当前帧的渲染和绘制都很耗时
当两张图表都是红色时,就要开始对 UI 线程(Dart VM)进行诊断了。


显示性能图层

Displaying the performance overlay

你可以用如下方法显示性能图层:

You can toggle display of the performance overlay as follows:

  • 使用 Flutter Inspector

    Using the Flutter Inspector

  • 从命令行启动

    From the command line

  • 写入代码

    Programmatically

使用 Flutter inspector

From the Flutter inspector

打开 PerformanceOverlay widget 最简单的方法是 IDE 中 Flutter 插件提供的 Flutter inspector。运行应用时会默认打开 Inspector 的窗口。如果没有打开,可以用下面的方法打开。

The easiest way to enable the PerformanceOverlay widget is in the Flutter inspector, which is available through the Flutter plugin for your IDE. The Inspector view opens by default when running an application. If the inspector isn’t open, you can display it as follows.

Android Studio 和 IntelliJ IDEA:

In Android Studio and IntelliJ IDEA:

  1. 选择 View > Tool Windows > Flutter Inspector

    Select View > Tool Windows > Flutter Inspector.

  2. 在工具栏中选择书架图标 (bookshelf-like icon)。

    In the toolbar, select the icon that looks like a bookshelf (bookshelf-like icon).

    IntelliJ Flutter inspector window

Flutter Inspector 在 Android Studio 和 IntelliJ 中都可以使用。了解更多可以使用 Inspector 做的事情,可以参阅 Widget inspector 文档,以及 DartConf 2018 的 Flutter Inspector talk

The Flutter Inspector is available in Android Studio and IntelliJ. Learn more about what the Inspector can do in the Widget inspector doc, as well as the Flutter Inspector talk presented at DartConf 2018.

In VS Code

VS Code

  1. 选择 View > Command Palette… 来打开 command palette。

    Select View > Command Palette… to bring up the command palette.

  2. 在文本框中输入“performance”并在弹出列表中选中 Toggle Performance Overlay。如果命令不可用,请确保应用在运行状态。

    In the text field, enter “performance” and select Toggle Performance Overlay from the list that pops up. If this command isn’t available, make sure that the app is running.

命令行

From the Command line

使用 p 参数触发性能图层。

Toggle the performance overlay using the P key from the command line.

代码控制

Programmatically

可以通过在 MaterialApp 或者 WidgetsApp 的构造方法中设置 showPerformanceOverlay 属性为 true 来展示 PerformanceOverlay widget:

You can programmatically enable the PerformanceOverlay widget by setting the showPerformanceOverlay property to true on the MaterialApp or WidgetsApp constructor:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showPerformanceOverlay: true,
      title: 'My Awesome App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'My Awesome App'),
    );
  }
}

可能读者已经对 Flutter Gallery 样例应用相当熟悉了。要在 Flutter Gallery 中使用性能图层,请使用与 Flutter 一起安装的 examples 目录的副本在分析模式下运行应用。应用的代码中已经写好了通过应用菜单动态触发图层,同时允许对 saveLayer 的调用和当前已缓存的图片的检查。

You are probably familiar with the Flutter Gallery example app. To use the performance overlay with Flutter Gallery, use the copy in the examples directory that was installed with Flutter, and run the app in profile mode. The program is written so that the app menu allows you to dynamically toggle the overlay, as well as enable checks for calls to saveLayer and the presence of cached images.

定位 UI 图表中的问题

Identifying problems in the UI graph

如果性能图层的 UI 图表显示红色,就要从分析 Dart VM 开始着手了,即使 GPU 图表同样显示红色。

If the performance overlay shows red in the UI graph, start by profiling the Dart VM, even if the GPU graph also shows red.

使用 Dart DevTool 进行性能分析

Performance profiling with Dart DevTools

Dart DevTool 提供诸如性能分析、堆测试以及显示代码覆盖率等功能。DevTool 的 timeline 界面可以让开发者逐帧分析应用的 UI 性能。

Dart DevTools provides features like profiling, examining the heap, and displaying code coverage. DevTool’s timeline view allows you to investigate the UI performance of your application on a frame-by-frame basis.

定位 GPU 图表中的问题

Identifying problems in the GPU graph

有些情况下界面的图层树构造起来虽然容易,但在 GPU 线程下渲染却很耗时。这种情况发生时,UI 图表没有红色,但 GPU 图表会显示红色。这时需要找出代码中导致渲染缓慢的原因。特定类型的负载对 GPU 来说会更加复杂。可能包括不必要的对 saveLayer 的调用,许多对象间的复杂操作,还可能是特定情形下的裁剪或者阴影。

Sometimes a scene results in a layer tree that is easy to construct, but expensive to render on the GPU thread. When this happens, the UI graph has no red, but the GPU graph shows red. In this case, you’ll need to figure out what your code is doing that is causing rendering code to be slow. Specific kinds of workloads are more difficult for the GPU. They might involve unnecessary calls to saveLayer, intersecting opacities with multiple objects, and clips or shadows in specific situations.

如果推断的原因是动画中的卡顿的话,可以使用 timeDilation 属性来极大地放慢动画。

If you suspect that the source of the slowness is during an animation, use the timeDilation property to greatly slow the animation down.

也可以使用 Flutter Inspector 来减慢动画速度。在 inspector 的 gear 菜单下选中 Enable Slow Animations。如果想对动画速度进行更多操作,请在代码中设置 timeDilation 属性。

You can also slow the animation speed using the Flutter Inspector. In the inspector’s gear menu, select Enable Slow Animations. If you want more control of the animation speed, set the timeDilation property in your code.

卡顿是第一帧发生的还是贯穿整个动画过程呢?如果是整个动画过程的话,会是裁剪导致的么?也许有可以替代裁剪的方法来绘制场景。比如说,不透明图层的长方形中用尖角来取代圆角裁剪。如果是一个静态场景的淡入、旋转或者其他操作,可以尝试使用 RepaintBoundary

Is the slowness on the first frame, or on the whole animation? If it’s the whole animation, is clipping causing the slow down? Maybe there’s an alternative way of drawing the scene that doesn’t use clipping. For example, overlay opaque corners onto a square instead of clipping to a rounded rectangle. If it’s a static scene that’s being faded, rotated, or otherwise manipulated, a RepaintBoundary might help.

检查屏幕之外的视图

Checking for offscreen layers

saveLayer 方法是 Flutter 框架中最重量的操作之一。更新屏幕时这个方法很有用,但它可能使应用变慢,如果不是必须的话,应该避免使用这个方法。即便没有显式地调用 saveLayer,也可能在其他操作中间接调用了该方法。可以使用 PerformanceOverlayLayer.checkerboardOffscreenLayers 开关来检查场景是否使用了 saveLayer

The saveLayer method is one of the most expensive methods in the Flutter framework. It’s useful when applying post-processing to the scene, but it can slow your app and should be avoided if you don’t need it. Even if you don’t call saveLayer explicitly, implicit calls might happen on your behalf. You can check whether your scene is using saveLayer with the PerformanceOverlayLayer.checkerboardOffscreenLayers switch.

打开开关之后,运行应用并检查是否有图像的轮廓闪烁。如果有新的帧渲染的话,容器就会闪烁。举个例子,也许有一组对象的透明度要使用 saveLayer 来渲染。在这种情况下,相比通过 widget 树中高层次的父 widget 操作,单独对每个 widget 来应用透明度可能性能会更好。其他可能大量消耗资源的操作也同理,比如裁剪或者阴影。

Once the switch is enabled, run the app and look for any images that are outlined with a flickering box. The box flickers from frame to frame if a new frame is being rendered. For example, perhaps you have a group of objects with opacities that are rendered using saveLayer. In this case, it’s probably more performant to apply an opacity to each individual widget, rather than a parent widget higher up in the widget tree. The same goes for other potentially expensive operations, such as clipping or shadows.

当遇到对 saveLayer 的调用时,先问问自己:

When you encounter calls to saveLayer, ask yourself these questions:

  • 应用是否需要这个效果?

    Does the app need this effect?

  • 可以减少调用么?

    Can any of these calls be eliminated?

  • 可以对单独元素操作而不是一组元素么?

    Can I apply the same effect to an individual element instead of a group?

检查没有缓存的图像

Checking for non-cached images

使用 RepaintBoundary 来缓存图片是个好主意, 当需要的时候

Caching an image with RepaintBoundary is good, when it makes sense.

从资源的角度看,最重量级的操作之一是用图像文件来渲染纹理。首先,需要从持久存储中取出压缩图像,然后解压缩到宿主存储中(GPU 存储),再传输到设备存储器中(RAM)。

One of the most expensive operations, from a resource perspective, is rendering a texture using an image file. First, the compressed image is fetched from persistent storage. The image is decompressed into host memory (GPU memory), and transferred to device memory (RAM).

也就是说,图像的 I/O 操作是重量级的。缓存提供了复杂层次的快照,这样就可以方便地渲染到随后的帧中。 因为光栅缓存入口的构建需要大量资源,同时增加了 GPU 存储的负载,所以只在必须时才缓存图片。

In other words, image I/O can be expensive. The cache provides snapshots of complex hierarchies so they are easier to render in subsequent frames. Because raster cache entries are expensive to construct and take up loads of GPU memory, cache images only where absolutely necessary.

打开 PerformanceOverlayLayer.checkerboardRasterCacheImages 开关可以检查哪些图片被缓存了。

You can see which images are being cached by enabling the PerformanceOverlayLayer.checkerboardRasterCacheImages switch.

运行应用来查看使用随机颜色网格渲染的图像,标识被缓存的图像。当和场景交互时,网格里的图片应该是静止的—代表重新缓存图片的闪烁视图不应该出现。

Run the app and look for images rendered with a randomly colored checkerboard, indicating that the image is cached. As you interact with the scene, the checkerboarded images should remain constant—you don’t want to see flickering, which would indicate that the cached image is being re-cached.

大多数情况下,开发者都希望在网格里看到的是静态图片,而不是非静态图片。如果静态图片没有被缓存,可以将其放到 RepaintBoundary widget 中来缓存。虽然引擎也可能忽略 repaint boundary,如果它认为图像还不够复杂的话。

In most cases, you want to see checkerboards on static images, but not on non-static images. If a static image isn’t cached, you can cache it by placing it into a RepaintBoundary widget. Though the engine might still ignore a repaint boundary if it thinks the image isn’t complex enough.

检视 widget 重建性能

Viewing the widget rebuild profiler

Flutter 框架的设计使得构建达不到 60fps 流畅度的应用变得困难。通常情况下如果卡顿,就是因为每一帧被重建的 UI 比需求更多的简单 bug。Widget rebuild profiler 可以帮助调试和修复这些问题引起的 bug。

The Flutter framework is designed to make it hard to create applications that are not 60fps and smooth. Often, if you have jank, it’s because there is a simple bug causing more of the UI to be rebuilt each frame than required. The Widget rebuild profiler helps you debug and fix performance problems due to these sorts of bugs.

可以检视 widget inspector 中当前屏幕和帧下的 widget 重建数量。了解细节,可以参考 在 Android Studio 或类 IntelliJ 里开发 Flutter 应用 中的 显示性能数据

You can view the widget rebuilt counts for the current screen and frame in the widget inspector. For details on how to do this, see Show performance data in the Android Studio / IntelliJ page.

调试参数

Debug flags

Flutter 提供了大量的调试参数和功能来帮助开发者在开发环节的各个性能点调试应用。这些特性必须在调试模式下编译。下面未完成的列表高亮了一些 rendering library 中在调试性能问题时最有用的参数(和一个方法)。

Flutter provides a wide variety of debug flags and functions to help you debug your app at various points along the development cycle. To use these features, you must compile in debug mode. The following list, while not complete, highlights some of the more useful flags (and one function) from the rendering library for debugging performance issues.

可以在编辑框架代码的时候设置这些参数,或者通过导入 module 并在 main() 方法中设置,然后通过热重启应用。

You can set these flags either by editing the framework code, or by importing the module and setting the value in your main() method, following by a hot restart.

  • debugDumpRenderTree()
    在 widget inspector 中检视渲染树,而不是使用这个参数转储渲染树到文件。使用 widget inspector 并选择 Render Tree 标签。

    debugDumpRenderTree()
    Rather than using this flag to dump the render tree to a file, view the render tree in the widget inspector. To do so, bring up the widget inspector and select the Render Tree tab.

  • debugPaintLayerBordersEnabled
  • debugRepaintRainbowEnabled
    可以在 widget inspector 打开 More Actions 菜单,并选择 Show Repaint Rainbow 来开启这个参数。如果任何静态 widget 在循环七彩跑马灯,(比如说一个静态的头部控件),这些区域就有着额外的重绘边界。

    debugRepaintRainbowEnabled
    You can enable this flag in the widget inspector by bringing up the More Actions menu, and selecting Show Repaint Rainbow. If any static widgets are rotating through the colors of the rainbow (for example, a static header), those areas are candidates for adding repaint boundaries.

  • debugPrintMarkNeedsLayoutStack
    如果发现比预期更多的 layout,打开这个参数,(比如说时间轴上、分析文件中、或者是在一个 layout 方法的 print 状态内)。开启后,控制台会大量输出堆栈跟踪信息来展示为什么每个渲染对象被 layout 标记为 dirty。

    debugPrintMarkNeedsLayoutStack
    Enable this flag if you’re seeing more layouts than you expect (for example, on the timeline, on a profile, or from a print statement inside a layout method). Once enabled, the console is flooded with stack traces showing why each render object is being marked dirty for layout.

  • debugPrintMarkNeedsPaintStacks
    debugPrintMarkNeedsLayoutStack相似,作用于过度绘制。

    debugPrintMarkNeedsPaintStacks
    Similar to debugPrintMarkNeedsLayoutStack, but for excess painting.

可以在 调试 Flutter 应用 中了解到其他的调试参数。

You can learn about other debug flags in Debugging Flutter apps.

评分

Benchmarking

可以通过编写评分测试来测量和追踪应用的性能。Flutter Driver 库提供了对评分的支持。基于这套测试框架就可以生成以下几项的测试标准:

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 to track the following:

  • 卡顿

    Jank

  • 下载大小

    Download size

  • 电池性能

    Battery efficiency

  • 启动时间

    Startup time

追踪这些评分可以在回归测试中了解对性能的不利影响。

Tracking these benchmarks allows you to be informed when a regression is introduced that adversely affects performance.

了解更多,请参考 测试 Flutter 应用 中的 集成测试 一节。

For more information, see Integration testing, a section in Testing Flutter apps.

更多信息

More information

以下链接提供了关于 Flutter 工具的使用和 Flutter 调试的更多信息:

The following resources provide more information on using Flutter’s tools and debugging in Flutter: