调试 Flutter 应用

有很多工具和特性可以帮助调试 Flutter 应用程序,如下列举了一些:

  • 开发者工具,是一套运行在浏览器的性能及分析工具。

  • Android Studio/IntelliJVS Code(借助 Flutter 和 Dart 插件)支持内置的源代码调试器,可以设置断点,单步调试,检查数值。

  • Flutter inspector,是开发者工具提供的 widget 检查器,也可直接在 Android Studio 和 IntelliJ 中使用(借助 Flutter 插件)。检查器可以可视化展现 widget 树,查看单个 widget 及其属性值,开启性能图层,等等。

开发者工具

要调试及分析应用,开发者工具可能是你的首选。开发者工具运行在浏览器,支持以下特性:

  • 源代码调试器

  • widget 检查器,展示可视化的 widget 树; “widget select” 模式,在应用中选择一个 widget,会在 widget 树直接定位到它的位置。

  • 内存分析

  • 时间线视图,支持跟踪,导入及导出跟踪信息

  • 日志视图

如果你在 debug 模式profile 模式 运行,那么可以在浏览器打开开发者工具连接到你的应用。开发者工具不能用在以 release 模式 编译的应用,因为调试和分析信息都被删除了。

如果你要用开发者工具分析应用,需确保使用 性能模式。否则,分析的主要输出将会是用于验证框架中各种不变式的调试断言(查看 debug 模式断言)。

GIF showing DevTools features

想获取更多信息,请查看 开发者工具 文档。

设置断点

要设置断点,可以直接在 IDE 或编辑器(比如 Android Studio/IntelliJVS Code)、 开发者工具调试器 设置,或者 通过编码的方式设置

Dart 分析器

如果你使用的是 Flutter 推荐的 IDE 或编辑器,则自带的 Dart 分析器默认会检查代码,并发现可能的错误。

如果你使用命令行,则可以使用 flutter analyze 检查代码。

Dart 分析器非常依赖你在代码中添加的类型注解,以帮助跟踪问题。建议您在各个地方都加上注解(避免 var,无类型参数,无类型 list 字面量,等等),因为这是跟踪问题最快且最不痛苦的方式。

想获取更多信息,请查看 使用 Dart 分析器

日志

另一个有用的调试工具是日志。通过 编码 配置日志,然后在开发者工具中的 日志视图 或控制台查看输出。

调试应用层

Flutter 采用分层架构,包括 widget、渲染和绘制等层。想获取更多信息和视频,请查看 GitHub wiki 上的 The Framework architecture,和社区文章 The Layer Cake

Flutter widget 检查器提供了 widget 树的视觉展现,如果你想要更多细节,或关于 wiget、层级或渲染树的详尽文本转储,请查看 添加输出代码的方式调试 Flutter 应用 页面的 调试标志:应用层 部分。

Debug 模式断言

在开发过程中,强烈建议您使用 Flutter 的 debug 模式。如果你是用 Android Studio 的 bug 图标运行,或者在命令行执行 flutter run,则默认会使用 debug 模式。有些工具通过 --enable-assets 命令行标志可以支持断言语句。

在此模式,Dart 断言语句被开启, Flutter 框架在执行时会计算每一个遇到的断言语句的参数,当结果是 false 时抛出异常。如此一来,开发者可以控制不变式检查的开启或关闭,相应的性能损耗将只发生在调试期间。

有不变式被违反时,它会被报告给控制台,并携带一些帮助跟踪问题源的上下文信息。

想获取更多信息,请查看 探索 Dart 语言 中的 断言 部分。

调试动画

调试动画最简单的方法是让它们变慢。 Flutter inspector 提供一个 放慢动画(Slow Animations) 的按钮,你也可以 在代码中放慢动画

想获取更多关于调试动画卡顿的信息,请查看 Flutter 性能分析

Measuring app startup time

测量应用启动时间

要收集有关 Flutter 应用程序启动所需时间的详细信息,可以在运行 flutter run 时使用 trace-startupprofile 选项。

$ flutter run --trace-startup --profile

跟踪输出被保存到 Flutter 工程目录在 build 目录下,一个名为 start_up_info.json 的 JSON 文件中。输出列出了从应用程序启动到这些跟踪事件(以微秒捕获)所用的时间:

  • 进入 Flutter 引擎时

  • 展示应用第一帧时

  • 初始化Flutter框架时

  • 完成Flutter框架初始化时

例如:

{
  "engineEnterTimestampMicros": 96025565262,
  "timeToFirstFrameMicros": 2171978,
  "timeToFrameworkInitMicros": 514585,
  "timeAfterFrameworkInitMicros": 1657393
}

Tracing Dart code

跟踪 Dart 代码性能

要进行性能跟踪,你可以使用开发者工具的 时间线视图。时间线视图还支持导入和导出跟踪文件。想要获取更多信息,请查看 时间线视图

你也可以 在代码中跟踪,不过这些跟踪信息无法导入到开发者模式的时间线视图。

跟踪时请确保在 性能模式 运行应用,这样才能保证运行时性能特征同你最终产品高度一致。

性能图层

要图形化展现你应用的性能,可以开启性能图层。你可以在 Flutter inspector 中点击 Performance Overlay 按钮。

你也可以 在代码中 开启该图层。

关于如何解析图层中的图形,请查看 Flutter 性能分析 中的 性能图层 部分。

调试标志

大部分情况,你不需要直接使用调试标志,因为可以在 开发者工具 找到最有用的调试功能。但是如果你偏好直接使用调试标志,请查看 添加输出代码的方式调试 Flutter 应用 中的 调试标志:性能 部分。

常见问题

下面是一些在 macOS 上遇到的问题。

“句柄数超出系统限制” 异常 (macOS)

mac OS 在同一时间可以打开多少句柄的默认限制数相当低。如果你达到这个极限,可以用 ulimit 命令增加可用句柄的数量:

ulimit -S -n 2048

如果您使用 Travis 或 Cirrus 进行测试,请通过在 flutter/.travis.yml 或 flutter/.cirrus.yml 中增加同样的命令来增加它们可以打开的句柄数量。

被标记为 const 的相同 Widget 应被视为同一对象,然而却并没有

在 debug 模式下,(由于 Dart 的常量去重策略)你也许会发现两个 const 的 widget 长得并不完全一样。

例如,下面的代码应该打印 1:

print(<Widget>{
  // this is the syntax for a Set<Widget> literal
  const SizedBox(),
  const SizedBox(),
}.length);

这段代码应该打印 1(而不是 2),这是由于两个常量相同且在同一个 set 中(实际上分析器抱怨 “集合文字中的两个元素不应相等”)。正如我们所期待的那样,在 release 模式下构建的时候,它确实打印了 1。然而,在 debug 模式下它却打印了 2。这是由于 Flutter tool 在编译期向 Widget 的构造器注入了源位置,所以下面的代码有效:

print(<Widget>{
  const SizedBox(/* location: Location(file: 'foo.dart', line: 12) */),
  const SizedBox(/* location: Location(file: 'foo.dart', line: 13) */),
}.length);

上面的代码在结果中的实例不同,故它们在 set 中并没有重复。我们使用注入信息汇报相关 widget 的创建信息,使得 widget 出现异常时错误消息会更加清晰。不幸的是,它会导致相同常量在编译期变为不同实例。

要关闭此行为,请在运行 flutter run 命令的同时传 --no-track-widget-creation。有了这个标记,代码将会在 debug 和 release 模式下打印 1,而错误消息这边会有一条消息说,除非打开 widget 创建跟踪器,否则我们将无法提供完整的信息。

你也可以查看:

其他资源

以下是其他一些有用的文档: