目录
0 引言
1 调试Flutter应用
1.1 日志与断点
1.1.1 debugger() 声明
1.1.2 print和debugPrint
1.1.3 调试模式、中间模式、发布模式
1.1.4 断点
1.2 调试应用程序层
1.2.1 转储Widgets树
1.2.2 转储渲染树
1.2.3 转储Layer树
1.2.4 转储语义树
1.2.5 调度(打印帧的开始和结束)
1.2.6 可视化调试
1.2.7 调试动画
1.2.8 调试性能问题
1.2.9 统计应用启动时间
1.2.10 跟踪Dart代码性能
1.3 官方可视化调试工具DevTools
0 引言
本文是对第二版序 | 《Flutter实战·第二版》 (flutterchina.club)的学习和总结。
1 调试Flutter应用
1.1 日志与断点
1.1.1 debugger()
声明
当使用Dart Observatory(调试模式时自动启用),可以使用debugger()
语句插入编程式断点。
import 'dart:developer';
/*
debugger()语句采用一个可选的when参数,可以指定该参数仅在特定条件为真时中断
*/
void someFunction(double offset) {
debugger(when: offset > 30.0);
// ...
}
1.1.2 print和
debugPrint
- Dart
print()
功能将输出到系统控制台,可以使用flutter logs
来查看它。如果一次输出太多,Android有时会丢弃一些日志行。- 推荐使用Flutter的
foundation
库中的debugPrint(),它封装了 print,将一次输出的内容长度限制在一个级别(内容过多时会分批输出),避免被Android内核丢弃。
1.1.3 调试模式、中间模式、发布模式
- 在Flutter应用调试过程中,Dart
assert
语句被启用,当某个规则被违反时,就会在控制台打印错误日志,并带上一些上下文信息来帮助追踪问题的根源。- 发布模式会关闭Observatory调试器,使用
flutter run --release
运行应用程序即可打开发布模式。- 中间模式“profile mode”可以关闭除Observatory之外所有的调试辅助工具,使用
flutter run --profile
运行应用程序即可打开中间模式。
1.1.4 断点
开发过程中,断点是最实用的调试工具之一,以 Android Studio 为例:比如在 93 行打了一个断点,一旦代码执行到这一行就会暂停,这时我们可以看到当前上下文所有变量的值,然后可以选择一步一步的执行代码。
1.2 调试应用程序层
Flutter框架的每一层都提供了将其当前状态或事件转储(dump)到控制台(使用debugPrint
)的功能。
1.2.1 转储Widgets树
- 要转储Widgets树的状态,请调用debugDumpApp()
- 需要在调用
runApp()
之后调用- 不能在
build()
方法内调用- 不要在布局或绘制阶段调用
- 从frame 回调或事件处理器中调用是最佳方案
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: AppHome(),
),
);
}
class AppHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: TextButton(
onPressed: () {
// 当按钮从被按下变为被释放时debugDumpApp()被调用
debugDumpApp();
},
child: Text('Dump App'),
),
),
);
}
}
调用后输出这样的内容(精确的细节会根据框架的版本、设备的大小等等而变化):
I/flutter ( 6559): WidgetsFlutterBinding - CHECKED MODE
I/flutter ( 6559): RenderObjectToWidgetAdapter<RenderBox>([GlobalObjectKey RenderView(497039273)]; renderObject: RenderView)
I/flutter ( 6559): └MaterialApp(state: _MaterialAppState(1009803148))
I/flutter ( 6559): └ScrollConfiguration()
I/flutter ( 6559): └AnimatedTheme(duration: 200ms; state: _AnimatedThemeState(543295893; ticker inactive; ThemeDataTween(ThemeData(Brightness.light Color(0xff2196f3) etc...) → null)))
I/flutter ( 6559): └Theme(ThemeData(Brightness.light Color(0xff2196f3) etc...))
I/flutter ( 6559): └WidgetsApp([GlobalObjectKey _MaterialAppState(1009803148)]; state: _WidgetsAppState(552902158))
I/flutter ( 6559): └CheckedModeBanner()
I/flutter ( 6559): └Banner()
I/flutter ( 6559): └CustomPaint(renderObject: RenderCustomPaint)
I/flutter ( 6559): └DefaultTextStyle(inherit: true; color: Color(0xd0ff0000); family: "monospace"; size: 48.0; weight: 900; decoration: double Color(0xffffff00) TextDecoration.underline)
I/flutter ( 6559): └MediaQuery(MediaQueryData(size: Size(411.4, 683.4), devicePixelRatio: 2.625, textScaleFactor: 1.0, padding: EdgeInsets(0.0, 24.0, 0.0, 0.0)))
I/flutter ( 6559): └LocaleQuery(null)
I/flutter ( 6559): └Title(color: Color(0xff2196f3))
... #省略剩余内容
1.2.2 转储渲染树
- 调试布局问题时,转储Widget树可能不够详细,需要转储渲染树
- 要转储渲染树,请调用
debugDumpRenderTree()
- 要调用
debugDumpRenderTree()
,需要添加import'package:flutter/rendering.dart'
对于上面的例子,它会输出:
I/flutter ( 6559): RenderView
I/flutter ( 6559): │ debug mode enabled - android
I/flutter ( 6559): │ window size: Size(1080.0, 1794.0) (in physical pixels)
I/flutter ( 6559): │ device pixel ratio: 2.625 (physical pixels per logical pixel)
I/flutter ( 6559): │ configuration: Size(411.4, 683.4) at 2.625x (in logical pixels)
I/flutter ( 6559): │
I/flutter ( 6559): └─child: RenderCustomPaint
I/flutter ( 6559): │ creator: CustomPaint ← Banner ← CheckedModeBanner ←
I/flutter ( 6559): │ WidgetsApp-[GlobalObjectKey _MaterialAppState(1009803148)] ←
I/flutter ( 6559): │ Theme ← AnimatedTheme ← ScrollConfiguration ← MaterialApp ←
I/flutter ( 6559): │ [root]
I/flutter ( 6559): │ parentData: <none>
I/flutter ( 6559): │ constraints: BoxConstraints(w=411.4, h=683.4)
I/flutter ( 6559): │ size: Size(411.4, 683.4)
... # 省略
1.2.3 转储Layer树
- 调试合成问题,调用debugDumpLayerTree()
- 可以理解为渲染树是可以分层的,而最终绘制需要将不同的层合成起来,而Layer则是绘制时需要合成的层
对于上面的例子,它会输出下图内容。这是根Layer
的toStringDeep
输出的。根部的变换是应用设备像素比的变换; 在这种情况下,每个逻辑像素代表3.5个设备像素。
I/flutter : TransformLayer
I/flutter : │ creator: [root]
I/flutter : │ offset: Offset(0.0, 0.0)
I/flutter : │ transform:
I/flutter : │ [0] 3.5,0.0,0.0,0.0
I/flutter : │ [1] 0.0,3.5,0.0,0.0
I/flutter : │ [2] 0.0,0.0,1.0,0.0
I/flutter : │ [3] 0.0,0.0,0.0,1.0
I/flutter : │
I/flutter : ├─child 1: OffsetLayer
I/flutter : │ │ creator: RepaintBoundary ← _FocusScope ← Semantics ← Focus-[GlobalObjectKey MaterialPageRoute(560156430)] ← _ModalScope-[GlobalKey 328026813] ← _OverlayEntry-[GlobalKey 388965355] ← Stack ← Overlay-[GlobalKey 625702218] ← Navigator-[GlobalObjectKey _MaterialAppState(859106034)] ← Title ← ⋯
I/flutter : │ │ offset: Offset(0.0, 0.0)
I/flutter : │ │
I/flutter : │ └─child 1: PictureLayer
I/flutter : │
I/flutter : └─child 2: PictureLayer
1.2.4 转储语义树
- 语义树:呈现给系统可访问性API的树
- 转储语义树,要调用debugDumpSemanticsTree()
- 要调用debugDumpSemanticsTree() ,必须首先启用辅助功能,例如启用系统辅助工具或
SemanticsDebugger
。
对于上面的例子,它会输出:
I/flutter : SemanticsNode(0; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter : ├SemanticsNode(1; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter : │ └SemanticsNode(2; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4); canBeTapped)
I/flutter : └SemanticsNode(3; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4))
I/flutter : └SemanticsNode(4; Rect.fromLTRB(0.0, 0.0, 82.0, 36.0); canBeTapped; "Dump App")
1.2.5 调度(打印帧的开始和结束)
- 切换debugPrintBeginFrameBanner和debugPrintEndFrameBanner布尔值可以将帧的开始和结束打印到控制台,找出相对于帧的开始/结束事件发生的位置。
- debugPrintScheduleFrameStacks可以用来打印导致当前帧被调度的调用堆栈
例如:
I/flutter : ▄▄▄▄▄▄▄▄ Frame 12 30s 437.086ms ▄▄▄▄▄▄▄▄
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
1.2.6 可视化调试
所有这些标志只能在调试模式下工作。
通常,Flutter框架中以“debug...
” 开头的任何内容都只能在调试模式下工作。
设置
debugPaintSizeEnabled
为true
以可视方式调试布局问题:
- 这是来自
rendering
库的布尔值。- 它可以在任何时候启用,并在为true时影响绘制。
- 设置它的最简单方法是在
void main()
的顶部设置。当它被启用时,所有的盒子都会得到一个明亮的深青色边框:
- padding(来自widget如Padding)显示为浅蓝色,
- 子widget周围有一个深蓝色框,
- 对齐方式(来自widget如Center和Align)显示为黄色箭头,
- 空白(如没有任何子节点的Container)以灰色显示。
debugPaintBaselinesEnabled做了类似的事情,但对于具有基线的对象,文字基线以绿色显示,表意(ideographic)基线以橙色显示。
debugPaintPointersEnabled标志打开一个特殊模式,任何正在点击的对象都会以深青色突出显示。 这可以帮助我们确定某个对象是否以某种不正确的方式进行hit测试(Flutter检测点击的位置是否有能响应用户操作的widget),例如,如果它实际上超出了其父项的范围,首先不会考虑通过hit测试。
debugPaintLayerBordersEnabled标志调试合成图层,例如以确定是否以及在何处添加
RepaintBoundary
widget。该标志用橙色或轮廓线标出每个层的边界,或者使用debugRepaintRainbowEnabled标志重绘时,该层会被一组旋转色所覆盖。
1.2.7 调试动画
调试动画最简单的方法是减慢它们的速度:
- 将timeDilation (opens new window)变量(在scheduler库中)设置为大于1.0的数字,例如50.0。
- 最好在应用程序启动时只设置一次。
- 如果在运行中更改它,尤其是在动画运行时将其值改小,则在观察时可能会出现倒退。
1.2.8 调试性能问题
- 要了解应用程序导致重新布局或重新绘制的原因,分别设置debugPrintMarkNeedsLayoutStacks和 debugPrintMarkNeedsPaintStacks标志。
- 每当渲染盒被要求重新布局和重新绘制时,这些都会将堆栈跟踪记录到控制台。
- 可以使用
services
库中的debugPrintStack()
方法按需打印堆栈痕迹。
1.2.9 统计应用启动时间
- 要收集有关Flutter应用程序启动所需时间的详细信息,
- 可以在运行
flutter run
时使用trace-startup
和profile
选项。
$ flutter run --trace-startup --profile
跟踪输出保存为
start_up_info.json
,在Flutter工程目录在build目录下。输出列出了从应用程序启动到这些跟踪事件(以微秒捕获)所用的时间:
- 进入Flutter引擎时.
- 展示应用第一帧时.
- 初始化Flutter框架时.
- 完成Flutter框架初始化时.
例如 :
{
"engineEnterTimestampMicros": 96025565262,
"timeToFirstFrameMicros": 2171978,
"timeToFrameworkInitMicros": 514585,
"timeAfterFrameworkInitMicros": 1657393
}
1.2.10 跟踪Dart代码性能
跟踪和测量Dart任意代码段的wall/CPU时间(类似Android上使用systrace):
- 运行
flutter run
时带有--profile
标志,- 使用
dart:developer
的Timeline工具来包含你想测试的代码块,- 打开你应用程序的Observatory timeline页面,在“Recorded Streams”中选择‘Dart’复选框,并执行你想测量的功能。
- 刷新页面将在Chrome的跟踪工具中显示应用按时间顺序排列的timeline记录。
Timeline.startSync('interesting function');
// iWonderHowLongThisTakes();
Timeline.finishSync();
1.3 官方可视化调试工具DevTools
Flutter DevTools 是一套 Dart 和 Flutter 的性能调试工具。
它将各种调试工具和能力集成在一起,并提供可视化调试界面。
可以用 Flutter DevTools 开发工具来实现的操作:
检查 Flutter 应用程序的 UI 组件布局和状态;
在 Flutter 应用程序中诊断 UI 性能过低的问题;
Flutter 和 Dart 应用的 CPU 性能检测;
为 Flutter 应用进行网络性能检测;
为 Flutter 或 Dart 应用进行源码级的调试;
在 Flutter 或 Dart 命令行应用中测试内存问题;
- 查看有关正在运行的Flutter或Dart命令行应用程序的常规日志和诊断信息。
查看正在运行的 Flutter 或 Dart 的命令行应用程序相关的常规日志和诊断信息。
在 Android Studio 上安装和运行开发者工具
在 VS Code 里安装和使用开发者工具