调试 Flutter Apps
有各种各样的工具和功能来帮助调试Flutter应用程序。
- Dart 分析器
- Dart Observatory (语句级的单步调试和分析器)
print
、debugPrint
、flutter logs
- 调试模式断言
- 调试应用程序层
- 可视化调试
- 调试动画
- 调试性能问题
- Performance Overlay
- Material grid
Dart 分析器
在运行应用程序前,请运行flutter analyze
测试你的代码。这个工具(它是dartanalyzer
工具的一个包装)将分析你的代码并帮助你发现可能的错误。
如果你使用IntelliJ的Flutter插件,那么已经自动启用了。
Dart分析器大量使用了代码中的类型注释来帮助追踪问题。我们鼓励您在任何地方使用它们(避免var、无类型的参数、无类型的列表文字等),因为这是追踪问题的最快的方式。
Dart Observatory (语句级的单步调试和分析器)
如果您使用flutter run
启动应用程序,那么当它运行时,您可以打开Observatory URL的Web页面(例如Observatory监听http://127.0.0.1:8100/),
直接使用语句级单步调试器连接到您的应用程序。如果您使用的是IntelliJ,则还可以使用其内置的调试器来调试您的应用程序。
Observatory 同时支持分析、检查堆等。有关Observatory的更多信息请参考Observatory 文档.
如果您使用Observatory进行分析,请确保通过--profile
选项来运行flutter run
命令来运行应用程序。
否则,配置文件中将出现的主要问题将是调试断言,以验证框架的各种不变量(请参阅下面的“调试模式断言”)。
debugger()
声明
当使用Dart Observatory(或另一个Dart调试器,例如IntelliJ IDE中的调试器)时,可以使用该debugger()
语句插入编程式断点。要使用这个,你必须添加import 'dart:developer';
到相关文件顶部。
debugger()
语句采用一个可选when
参数,您可以指定该参数仅在特定条件为真时中断,如下所示:
void someFunction(double offset) {
debugger(when: offset > 30.0);
// ...
}
print
、debugPrint
、flutter logs
Dart print()
功能将输出到系统控制台,您可以使用flutter logs
了查看它(基本上是一个包装adb logcat
)。
如果你一次输出太多,那么Android有时会丢弃一些日志行。为了避免这种情况,您可以使用Flutter的foundation
库中的debugPrint()
。
这是一个封装print,它将输出限制在一个级别,避免被Android内核丢弃。
Flutter框架中的许多类都有toString
实现。按照惯例,这些输出通常包括对象的runtimeType
单行输出,通常在表单中ClassName(more information about this instance…)。
树中使用的一些类也具有toStringDeep
,从该点返回整个子树的多行描述。已一些具有详细信息toString
的类会实现一个toStringShort
,它只返回对象的类型或其他非常简短的(一个或两个单词)描述。
调试模式断言
在开发过程中,强烈建议您使用Flutter的“调试”模式,有时也称为“checked”模式(译者语:Dart2.0后“checked”被废除,可以使用“strong” mode)。
如果您使用flutter run
运行程序。在这种模式下,Dart assert语句被启用,并且Flutter框架使用它来执行许多运行时检查来验证是否违反一些不可变的规则。
当一个不可变的规则被违反时,它被报告给控制台,并带有一些上下文信息来帮助追踪问题的根源。
要关闭调试模式并使用发布模式,请使用flutter run --release
运行您的应用程序。
这也关闭了Observatory调试器。一个中间模式可以关闭除Observatory之外所有调试辅助工具的,称为“profile mode”,用--profile
替代--release
即可。
调试应用程序层
Flutter框架的每一层都提供了将其当前状态或事件转储(dump)到控制台(使用debugPrint
)的功能。
Widget 层
要转储Widgets库的状态,请调用debugDumpApp()
。
只要应用程序已经构建了至少一次(即在调用build()
之后的任何时间),您可以在应用程序未处于构建阶段(即,不在build()
方法内调用 )的任何时间调用此方法(在调用runApp()
之后)。
如, 这个应用程序:
import 'package:flutter/material.dart';
void main() {
runApp(
new MaterialApp(
home: new AppHome(),
),
);
}
class AppHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Material(
child: new Center(
child: new FlatButton(
onPressed: () {
debugDumpApp();
},
child: new 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))
I/flutter ( 6559): └Navigator([GlobalObjectKey<NavigatorState> _WidgetsAppState(552902158)]; state: NavigatorState(240327618; tracking 1 ticker))
I/flutter ( 6559): └Listener(listeners: down, up, cancel; behavior: defer-to-child; renderObject: RenderPointerListener)
I/flutter ( 6559): └AbsorbPointer(renderObject: RenderAbsorbPointer)
I/flutter ( 6559): └Focus([GlobalKey 489139594]; state: _FocusState(739584448))
I/flutter ( 6559): └Semantics(container: true; renderObject: RenderSemanticsAnnotations)
I/flutter ( 6559): └_FocusScope(this scope has focus; focused subscope: [GlobalObjectKey MaterialPageRoute<Null>(875520219)])
I/flutter ( 6559): └Overlay([GlobalKey 199833992]; state: OverlayState(619367313; entries: [OverlayEntry@248818791(opaque: false; maintainState: false), OverlayEntry@837336156(opaque: false; maintainState: true)]))
I/flutter ( 6559): └_Theatre(renderObject: _RenderTheatre)
I/flutter ( 6559): └Stack(renderObject: RenderStack)
I/flutter ( 6559): ├_OverlayEntry([GlobalKey 612888877]; state: _OverlayEntryState(739137453))
I/flutter ( 6559): │└IgnorePointer(ignoring: false; renderObject: RenderIgnorePointer)
I/flutter ( 6559): │ └ModalBarrier()
I/flutter ( 6559): │ └Semantics(container: true; renderObject: RenderSemanticsAnnotations)
I/flutter ( 6559): │ └GestureDetector()
I/flutter ( 6559): │ └RawGestureDetector(state: RawGestureDetectorState(39068508; gestures: tap; behavior: opaque))
I/flutter ( 6559): │ └_GestureSemantics(renderObject: RenderSemanticsGestureHandler)
I/flutter ( 6559): │ └Listener(listeners: down; behavior: opaque; renderObject: RenderPointerListener)
I/flutter ( 6559): │ └ConstrainedBox(BoxConstraints(biggest); renderObject: RenderConstrainedBox)
I/flutter ( 6559): └_OverlayEntry([GlobalKey 727622716]; state: _OverlayEntryState(279971240))
I/flutter ( 6559): └_ModalScope([GlobalKey 816151164]; state: _ModalScopeState(875510645))
I/flutter ( 6559): └Focus([GlobalObjectKey MaterialPageRoute<Null>(875520219)]; state: _FocusState(331487674))
I/flutter ( 6559): └Semantics(container: true; renderObject: RenderSemanticsAnnotations)
I/flutter ( 6559): └_FocusScope(this scope has focus)
I/flutter ( 6559): └Offstage(offstage: false; renderObject: RenderOffstage)
I/flutter ( 6559): └IgnorePointer(ignoring: false; renderObject: RenderIgnorePointer)
I/flutter ( 6559): └_MountainViewPageTransition(animation: AnimationController(⏭ 1.000; paused; for MaterialPageRoute<Null>(/))➩ProxyAnimation➩Cubic(0.40, 0.00, 0.20, 1.00)➩Tween<Offset>(Offset(0.0, 1.0) → Offset(0.0, 0.0))➩Offset(0.0, 0.0); state: _AnimatedState(552160732))
I/flutter ( 6559): └SlideTransition(animation: AnimationController(⏭ 1.000; paused; for MaterialPageRoute<Null>(/))➩ProxyAnimation➩Cubic(0.40, 0.00, 0.20, 1.00)➩Tween<Offset>(Offset(0.0, 1.0) → Offset(0.0, 0.0))➩Offset(0.0, 0.0); state: _AnimatedState(714726495))
I/flutter ( 6559): └FractionalTranslation(renderObject: RenderFractionalTranslation)
I/flutter ( 6559): └RepaintBoundary(renderObject: RenderRepaintBoundary)
I/flutter ( 6559): └PageStorage([GlobalKey 619728754])
I/flutter ( 6559): └_ModalScopeStatus(active)
I/flutter ( 6559): └AppHome()
I/flutter ( 6559): └Material(MaterialType.canvas; elevation: 0; state: _MaterialState(780114997))
I/flutter ( 6559): └AnimatedContainer(duration: 200ms; has background; state: _AnimatedContainerState(616063822; ticker inactive; has background))
I/flutter ( 6559): └Container(bg: BoxDecoration())
I/flutter ( 6559): └DecoratedBox(renderObject: RenderDecoratedBox)
I/flutter ( 6559): └Container(bg: BoxDecoration(backgroundColor: Color(0xfffafafa)))
I/flutter ( 6559): └DecoratedBox(renderObject: RenderDecoratedBox)
I/flutter ( 6559): └NotificationListener<LayoutChangedNotification>()
I/flutter ( 6559): └_InkFeature([GlobalKey ink renderer]; renderObject: _RenderInkFeatures)
I/flutter ( 6559): └AnimatedDefaultTextStyle(duration: 200ms; inherit: false; color: Color(0xdd000000); family: "Roboto"; size: 14.0; weight: 400; baseline: alphabetic; state: _AnimatedDefaultTextStyleState(427742350; ticker inactive))
I/flutter ( 6559): └DefaultTextStyle(inherit: false; color: Color(0xdd000000); family: "Roboto"; size: 14.0; weight: 400; baseline: alphabetic)
I/flutter ( 6559): └Center(alignment: Alignment.center; renderObject: RenderPositionedBox)
I/flutter ( 6559): └FlatButton()
I/flutter ( 6559): └MaterialButton(state: _MaterialButtonState(398724090))
I/flutter ( 6559): └ConstrainedBox(BoxConstraints(88.0<=w<=Infinity, h=36.0); renderObject: RenderConstrainedBox relayoutBoundary=up1)
I/flutter ( 6559): └AnimatedDefaultTextStyle(duration: 200ms; inherit: false; color: Color(0xdd000000); family: "Roboto"; size: 14.0; weight: 500; baseline: alphabetic; state: _AnimatedDefaultTextStyleState(315134664; ticker inactive))
I/flutter ( 6559): └DefaultTextStyle(inherit: false; color: Color(0xdd000000); family: "Roboto"; size: 14.0; weight: 500; baseline: alphabetic)
I/flutter ( 6559): └IconTheme(color: Color(0xdd000000))
I/flutter ( 6559): └InkWell(state: _InkResponseState<InkResponse>(369160267))
I/flutter ( 6559): └GestureDetector()
I/flutter ( 6559): └RawGestureDetector(state: RawGestureDetectorState(175370983; gestures: tap; behavior: opaque))
I/flutter ( 6559): └_GestureSemantics(renderObject: RenderSemanticsGestureHandler relayoutBoundary=up2)
I/flutter ( 6559): └Listener(listeners: down; behavior: opaque; renderObject: RenderPointerListener relayoutBoundary=up3)
I/flutter ( 6559): └Container(padding: EdgeInsets(16.0, 0.0, 16.0, 0.0))
I/flutter ( 6559): └Padding(renderObject: RenderPadding relayoutBoundary=up4)
I/flutter ( 6559): └Center(alignment: Alignment.center; widthFactor: 1.0; renderObject: RenderPositionedBox relayoutBoundary=up5)
I/flutter ( 6559): └Text("Dump App")
I/flutter ( 6559): └RichText(renderObject: RenderParagraph relayoutBoundary=up6)
这是一个“扁平化”的树,显示了通过各种构建函数投影的所有widget(如果你在widget树的根中调用toStringDeepwidget
,这是你获得的树)。
你会看到很多在你的应用源代码中没有出现的widget,因为它们被框架的widget的build()
函数插入的。例如,InkFeature
是Material widget的一个实现细节 。
由于debugDumpApp()当按钮从被按下变为被释放时被调用,FlatButton对象同时调用setState()
,所以并因此将自己标记为脏。
这就是为什么如果你看转储,你会看到特定的对象标记为“dirty”。您还可以查看已注册了哪些手势监听器;
在这种情况下,一个单一的GestureDetector被列出,并且监听“tap”手势(“tap”是TapGestureDetector
的toStringShort
函数输出的)
如果您编写自己的widget,则可以通过覆盖debugFillProperties()
来添加信息。
将DiagnosticsProperty对象作为方法参数,并调用父类方法。
该函数是该toString
方法用来填充小部件描述信息的。
渲染层
如果您尝试调试布局问题,那么Widgets层的树可能不够详细。在这种情况下,您可以通过调用debugDumpRenderTree()
转储渲染树。
正如debugDumpApp()
,除布局或绘制阶段外,您可以随时调用此函数。作为一般规则,从frame 回调 或事件处理器中调用它是最佳解决方案。
要调用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)
I/flutter ( 6559): │
I/flutter ( 6559): └─child: RenderPointerListener
I/flutter ( 6559): │ creator: Listener ← Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): │ _WidgetsAppState(552902158)] ← Title ← LocaleQuery ← MediaQuery
I/flutter ( 6559): │ ← DefaultTextStyle ← CustomPaint ← Banner ← CheckedModeBanner ←
I/flutter ( 6559): │ WidgetsApp-[GlobalObjectKey _MaterialAppState(1009803148)] ←
I/flutter ( 6559): │ Theme ← AnimatedTheme ← ⋯
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)
I/flutter ( 6559): │ behavior: defer-to-child
I/flutter ( 6559): │ listeners: down, up, cancel
I/flutter ( 6559): │
I/flutter ( 6559): └─child: RenderAbsorbPointer
I/flutter ( 6559): │ creator: AbsorbPointer ← Listener ←
I/flutter ( 6559): │ Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): │ _WidgetsAppState(552902158)] ← Title ← LocaleQuery ← MediaQuery
I/flutter ( 6559): │ ← DefaultTextStyle ← CustomPaint ← Banner ← CheckedModeBanner ←
I/flutter ( 6559): │ WidgetsApp-[GlobalObjectKey _MaterialAppState(1009803148)] ←
I/flutter ( 6559): │ Theme ← ⋯
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)
I/flutter ( 6559): │ absorbing: false
I/flutter ( 6559): │
I/flutter ( 6559): └─child: RenderSemanticsAnnotations
I/flutter ( 6559): │ creator: Semantics ← Focus-[GlobalKey 489139594] ← AbsorbPointer
I/flutter ( 6559): │ ← Listener ← Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): │ _WidgetsAppState(552902158)] ← Title ← LocaleQuery ← MediaQuery
I/flutter ( 6559): │ ← DefaultTextStyle ← CustomPaint ← Banner ← CheckedModeBanner ←
I/flutter ( 6559): │ ⋯
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)
I/flutter ( 6559): │
I/flutter ( 6559): └─child: _RenderTheatre
I/flutter ( 6559): │ creator: _Theatre ← Overlay-[GlobalKey 199833992] ← _FocusScope ←
I/flutter ( 6559): │ Semantics ← Focus-[GlobalKey 489139594] ← AbsorbPointer ←
I/flutter ( 6559): │ Listener ← Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): │ _WidgetsAppState(552902158)] ← Title ← LocaleQuery ← MediaQuery
I/flutter ( 6559): │ ← DefaultTextStyle ← ⋯
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)
I/flutter ( 6559): │
I/flutter ( 6559): ├─onstage: RenderStack
I/flutter ( 6559): ╎ │ creator: Stack ← _Theatre ← Overlay-[GlobalKey 199833992] ←
I/flutter ( 6559): ╎ │ _FocusScope ← Semantics ← Focus-[GlobalKey 489139594] ←
I/flutter ( 6559): ╎ │ AbsorbPointer ← Listener ←
I/flutter ( 6559): ╎ │ Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): ╎ │ _WidgetsAppState(552902158)] ← Title ← LocaleQuery ← MediaQuery
I/flutter ( 6559): ╎ │ ← ⋯
I/flutter ( 6559): ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(w=411.4, h=683.4)
I/flutter ( 6559): ╎ │ size: Size(411.4, 683.4)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ ├─child 1: RenderIgnorePointer
I/flutter ( 6559): ╎ │ │ creator: IgnorePointer ← _OverlayEntry-[GlobalKey 612888877] ←
I/flutter ( 6559): ╎ │ │ Stack ← _Theatre ← Overlay-[GlobalKey 199833992] ← _FocusScope
I/flutter ( 6559): ╎ │ │ ← Semantics ← Focus-[GlobalKey 489139594] ← AbsorbPointer ←
I/flutter ( 6559): ╎ │ │ Listener ← Navigator-[GlobalObjectKey<NavigatorState>
I/flutter ( 6559): ╎ │ │ _WidgetsAppState(552902158)] ← Title ← ⋯
I/flutter ( 6559): ╎ │ │ parentData: not positioned; offset=Offset(0.0, 0.0)
I/flutter ( 6559): ╎ │ │ constraints: BoxConstraints(w=411.4, h=683.4)
I/flutter ( 6559): ╎ │ │ size: Size(411.4, 683.4)
I/flutter ( 6559): ╎ │ │ ignoring: false
I/flutter ( 6559): ╎ │ │ ignoringSemantics: implicitly false
I/flutter ( 6559): ╎ │ │
I/flutter ( 6559): ╎ │ └─child: RenderSemanticsAnnotations
I/flutter ( 6559): ╎ │ │ creator: Semantics ← ModalBarrier ← IgnorePointer ←
I/flutter ( 6559): ╎ │ │ _OverlayEntry-[GlobalKey 612888877] ← Stack ← _Theatre ←
I/flutter ( 6559): ╎ │ │ Overlay-[GlobalKey 199833992] ← _FocusScope ← Semantics ←
I/flutter ( 6559): ╎ │ │ Focus-[GlobalKey 489139594] ← AbsorbPointer ← Listener ← ⋯
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)
I/flutter ( 6559): ╎ │ │
I/flutter ( 6559): ╎ │ └─child: RenderSemanticsGestureHandler
I/flutter ( 6559): ╎ │ │ creator: _GestureSemantics ← RawGestureDetector ← GestureDetector
I/flutter ( 6559): ╎ │ │ ← Semantics ← ModalBarrier ← IgnorePointer ←
I/flutter ( 6559): ╎ │ │ _OverlayEntry-[GlobalKey 612888877] ← Stack ← _Theatre ←
I/flutter ( 6559): ╎ │ │ Overlay-[GlobalKey 199833992] ← _FocusScope ← Semantics ← ⋯
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)
I/flutter ( 6559): ╎ │ │
I/flutter ( 6559): ╎ │ └─child: RenderPointerListener
I/flutter ( 6559): ╎ │ │ creator: Listener ← _GestureSemantics ← RawGestureDetector ←
I/flutter ( 6559): ╎ │ │ GestureDetector ← Semantics ← ModalBarrier ← IgnorePointer ←
I/flutter ( 6559): ╎ │ │ _OverlayEntry-[GlobalKey 612888877] ← Stack ← _Theatre ←
I/flutter ( 6559): ╎ │ │ Overlay-[GlobalKey 199833992] ← _FocusScope ← ⋯
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)
I/flutter ( 6559): ╎ │ │ behavior: opaque
I/flutter ( 6559): ╎ │ │ listeners: down
I/flutter ( 6559): ╎ │ │
I/flutter ( 6559): ╎ │ └─child: RenderConstrainedBox
I/flutter ( 6559): ╎ │ creator: ConstrainedBox ← Listener ← _GestureSemantics ←
I/flutter ( 6559): ╎ │ RawGestureDetector ← GestureDetector ← Semantics ← ModalBarrier
I/flutter ( 6559): ╎ │ ← IgnorePointer ← _OverlayEntry-[GlobalKey 612888877] ← Stack ←
I/flutter ( 6559): ╎ │ _Theatre ← Overlay-[GlobalKey 199833992] ← ⋯
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)
I/flutter ( 6559): ╎ │ additionalConstraints: BoxConstraints(biggest)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child 2: RenderSemanticsAnnotations
I/flutter ( 6559): ╎ │ creator: Semantics ← Focus-[GlobalObjectKey
I/flutter ( 6559): ╎ │ MaterialPageRoute<Null>(875520219)] ← _ModalScope-[GlobalKey
I/flutter ( 6559): ╎ │ 816151164] ← _OverlayEntry-[GlobalKey 727622716] ← Stack ←
I/flutter ( 6559): ╎ │ _Theatre ← Overlay-[GlobalKey 199833992] ← _FocusScope ←
I/flutter ( 6559): ╎ │ Semantics ← Focus-[GlobalKey 489139594] ← AbsorbPointer ←
I/flutter ( 6559): ╎ │ Listener ← ⋯
I/flutter ( 6559): ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(w=411.4, h=683.4)
I/flutter ( 6559): ╎ │ size: Size(411.4, 683.4)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderOffstage
I/flutter ( 6559): ╎ │ creator: Offstage ← _FocusScope ← Semantics ←
I/flutter ( 6559): ╎ │ Focus-[GlobalObjectKey MaterialPageRoute<Null>(875520219)] ←
I/flutter ( 6559): ╎ │ _ModalScope-[GlobalKey 816151164] ← _OverlayEntry-[GlobalKey
I/flutter ( 6559): ╎ │ 727622716] ← Stack ← _Theatre ← Overlay-[GlobalKey 199833992] ←
I/flutter ( 6559): ╎ │ _FocusScope ← Semantics ← Focus-[GlobalKey 489139594] ← ⋯
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)
I/flutter ( 6559): ╎ │ offstage: false
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderIgnorePointer
I/flutter ( 6559): ╎ │ creator: IgnorePointer ← Offstage ← _FocusScope ← Semantics ←
I/flutter ( 6559): ╎ │ Focus-[GlobalObjectKey MaterialPageRoute<Null>(875520219)] ←
I/flutter ( 6559): ╎ │ _ModalScope-[GlobalKey 816151164] ← _OverlayEntry-[GlobalKey
I/flutter ( 6559): ╎ │ 727622716] ← Stack ← _Theatre ← Overlay-[GlobalKey 199833992] ←
I/flutter ( 6559): ╎ │ _FocusScope ← Semantics ← ⋯
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)
I/flutter ( 6559): ╎ │ ignoring: false
I/flutter ( 6559): ╎ │ ignoringSemantics: implicitly false
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderFractionalTranslation
I/flutter ( 6559): ╎ │ creator: FractionalTranslation ← SlideTransition ←
I/flutter ( 6559): ╎ │ _MountainViewPageTransition ← IgnorePointer ← Offstage ←
I/flutter ( 6559): ╎ │ _FocusScope ← Semantics ← Focus-[GlobalObjectKey
I/flutter ( 6559): ╎ │ MaterialPageRoute<Null>(875520219)] ← _ModalScope-[GlobalKey
I/flutter ( 6559): ╎ │ 816151164] ← _OverlayEntry-[GlobalKey 727622716] ← Stack ←
I/flutter ( 6559): ╎ │ _Theatre ← ⋯
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)
I/flutter ( 6559): ╎ │ translation: Offset(0.0, 0.0)
I/flutter ( 6559): ╎ │ transformHitTests: true
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderRepaintBoundary
I/flutter ( 6559): ╎ │ creator: RepaintBoundary ← FractionalTranslation ←
I/flutter ( 6559): ╎ │ SlideTransition ← _MountainViewPageTransition ← IgnorePointer ←
I/flutter ( 6559): ╎ │ Offstage ← _FocusScope ← Semantics ← Focus-[GlobalObjectKey
I/flutter ( 6559): ╎ │ MaterialPageRoute<Null>(875520219)] ← _ModalScope-[GlobalKey
I/flutter ( 6559): ╎ │ 816151164] ← _OverlayEntry-[GlobalKey 727622716] ← Stack ← ⋯
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)
I/flutter ( 6559): ╎ │ metrics: 83.3% useful (1 bad vs 5 good)
I/flutter ( 6559): ╎ │ diagnosis: this is a useful repaint boundary and should be kept
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderDecoratedBox
I/flutter ( 6559): ╎ │ creator: DecoratedBox ← Container ← AnimatedContainer ← Material
I/flutter ( 6559): ╎ │ ← AppHome ← _ModalScopeStatus ← PageStorage-[GlobalKey
I/flutter ( 6559): ╎ │ 619728754] ← RepaintBoundary ← FractionalTranslation ←
I/flutter ( 6559): ╎ │ SlideTransition ← _MountainViewPageTransition ← IgnorePointer ←
I/flutter ( 6559): ╎ │ ⋯
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)
I/flutter ( 6559): ╎ │ decoration:
I/flutter ( 6559): ╎ │ <no decorations specified>
I/flutter ( 6559): ╎ │ configuration: ImageConfiguration(bundle:
I/flutter ( 6559): ╎ │ PlatformAssetBundle@367106502(), devicePixelRatio: 2.625,
I/flutter ( 6559): ╎ │ platform: android)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderDecoratedBox
I/flutter ( 6559): ╎ │ creator: DecoratedBox ← Container ← DecoratedBox ← Container ←
I/flutter ( 6559): ╎ │ AnimatedContainer ← Material ← AppHome ← _ModalScopeStatus ←
I/flutter ( 6559): ╎ │ PageStorage-[GlobalKey 619728754] ← RepaintBoundary ←
I/flutter ( 6559): ╎ │ FractionalTranslation ← SlideTransition ← ⋯
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)
I/flutter ( 6559): ╎ │ decoration:
I/flutter ( 6559): ╎ │ backgroundColor: Color(0xfffafafa)
I/flutter ( 6559): ╎ │ configuration: ImageConfiguration(bundle:
I/flutter ( 6559): ╎ │ PlatformAssetBundle@367106502(), devicePixelRatio: 2.625,
I/flutter ( 6559): ╎ │ platform: android)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: _RenderInkFeatures
I/flutter ( 6559): ╎ │ creator: _InkFeature-[GlobalKey ink renderer] ←
I/flutter ( 6559): ╎ │ NotificationListener<LayoutChangedNotification> ← DecoratedBox
I/flutter ( 6559): ╎ │ ← Container ← DecoratedBox ← Container ← AnimatedContainer ←
I/flutter ( 6559): ╎ │ Material ← AppHome ← _ModalScopeStatus ← PageStorage-[GlobalKey
I/flutter ( 6559): ╎ │ 619728754] ← RepaintBoundary ← ⋯
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)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderPositionedBox
I/flutter ( 6559): ╎ │ creator: Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
I/flutter ( 6559): ╎ │ _InkFeature-[GlobalKey ink renderer] ←
I/flutter ( 6559): ╎ │ NotificationListener<LayoutChangedNotification> ← DecoratedBox
I/flutter ( 6559): ╎ │ ← Container ← DecoratedBox ← Container ← AnimatedContainer ←
I/flutter ( 6559): ╎ │ Material ← AppHome ← ⋯
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)
I/flutter ( 6559): ╎ │ alignment: Alignment.center
I/flutter ( 6559): ╎ │ widthFactor: expand
I/flutter ( 6559): ╎ │ heightFactor: expand
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderConstrainedBox relayoutBoundary=up1
I/flutter ( 6559): ╎ │ creator: ConstrainedBox ← MaterialButton ← FlatButton ← Center ←
I/flutter ( 6559): ╎ │ DefaultTextStyle ← AnimatedDefaultTextStyle ←
I/flutter ( 6559): ╎ │ _InkFeature-[GlobalKey ink renderer] ←
I/flutter ( 6559): ╎ │ NotificationListener<LayoutChangedNotification> ← DecoratedBox
I/flutter ( 6559): ╎ │ ← Container ← DecoratedBox ← Container ← ⋯
I/flutter ( 6559): ╎ │ parentData: offset=Offset(156.7, 323.7)
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(0.0<=w<=411.4, 0.0<=h<=683.4)
I/flutter ( 6559): ╎ │ size: Size(98.0, 36.0)
I/flutter ( 6559): ╎ │ additionalConstraints: BoxConstraints(88.0<=w<=Infinity, h=36.0)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderSemanticsGestureHandler relayoutBoundary=up2
I/flutter ( 6559): ╎ │ creator: _GestureSemantics ← RawGestureDetector ← GestureDetector
I/flutter ( 6559): ╎ │ ← InkWell ← IconTheme ← DefaultTextStyle ←
I/flutter ( 6559): ╎ │ AnimatedDefaultTextStyle ← ConstrainedBox ← MaterialButton ←
I/flutter ( 6559): ╎ │ FlatButton ← Center ← DefaultTextStyle ← ⋯
I/flutter ( 6559): ╎ │ parentData: <none>
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(88.0<=w<=411.4, h=36.0)
I/flutter ( 6559): ╎ │ size: Size(98.0, 36.0)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderPointerListener relayoutBoundary=up3
I/flutter ( 6559): ╎ │ creator: Listener ← _GestureSemantics ← RawGestureDetector ←
I/flutter ( 6559): ╎ │ GestureDetector ← InkWell ← IconTheme ← DefaultTextStyle ←
I/flutter ( 6559): ╎ │ AnimatedDefaultTextStyle ← ConstrainedBox ← MaterialButton ←
I/flutter ( 6559): ╎ │ FlatButton ← Center ← ⋯
I/flutter ( 6559): ╎ │ parentData: <none>
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(88.0<=w<=411.4, h=36.0)
I/flutter ( 6559): ╎ │ size: Size(98.0, 36.0)
I/flutter ( 6559): ╎ │ behavior: opaque
I/flutter ( 6559): ╎ │ listeners: down
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderPadding relayoutBoundary=up4
I/flutter ( 6559): ╎ │ creator: Padding ← Container ← Listener ← _GestureSemantics ←
I/flutter ( 6559): ╎ │ RawGestureDetector ← GestureDetector ← InkWell ← IconTheme ←
I/flutter ( 6559): ╎ │ DefaultTextStyle ← AnimatedDefaultTextStyle ← ConstrainedBox ←
I/flutter ( 6559): ╎ │ MaterialButton ← ⋯
I/flutter ( 6559): ╎ │ parentData: <none>
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(88.0<=w<=411.4, h=36.0)
I/flutter ( 6559): ╎ │ size: Size(98.0, 36.0)
I/flutter ( 6559): ╎ │ padding: EdgeInsets(16.0, 0.0, 16.0, 0.0)
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderPositionedBox relayoutBoundary=up5
I/flutter ( 6559): ╎ │ creator: Center ← Padding ← Container ← Listener ←
I/flutter ( 6559): ╎ │ _GestureSemantics ← RawGestureDetector ← GestureDetector ←
I/flutter ( 6559): ╎ │ InkWell ← IconTheme ← DefaultTextStyle ←
I/flutter ( 6559): ╎ │ AnimatedDefaultTextStyle ← ConstrainedBox ← ⋯
I/flutter ( 6559): ╎ │ parentData: offset=Offset(16.0, 0.0)
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(56.0<=w<=379.4, h=36.0)
I/flutter ( 6559): ╎ │ size: Size(66.0, 36.0)
I/flutter ( 6559): ╎ │ alignment: Alignment.center
I/flutter ( 6559): ╎ │ widthFactor: 1.0
I/flutter ( 6559): ╎ │ heightFactor: expand
I/flutter ( 6559): ╎ │
I/flutter ( 6559): ╎ └─child: RenderParagraph relayoutBoundary=up6
I/flutter ( 6559): ╎ │ creator: RichText ← Text ← Center ← Padding ← Container ←
I/flutter ( 6559): ╎ │ Listener ← _GestureSemantics ← RawGestureDetector ←
I/flutter ( 6559): ╎ │ GestureDetector ← InkWell ← IconTheme ← DefaultTextStyle ← ⋯
I/flutter ( 6559): ╎ │ parentData: offset=Offset(0.0, 10.0)
I/flutter ( 6559): ╎ │ constraints: BoxConstraints(0.0<=w<=379.4, 0.0<=h<=36.0)
I/flutter ( 6559): ╎ │ size: Size(66.0, 16.0)
I/flutter ( 6559): ╎ ╘═╦══ text ═══
I/flutter ( 6559): ╎ ║ TextSpan:
I/flutter ( 6559): ╎ ║ inherit: false
I/flutter ( 6559): ╎ ║ color: Color(0xdd000000)
I/flutter ( 6559): ╎ ║ family: "Roboto"
I/flutter ( 6559): ╎ ║ size: 14.0
I/flutter ( 6559): ╎ ║ weight: 500
I/flutter ( 6559): ╎ ║ baseline: alphabetic
I/flutter ( 6559): ╎ ║ "Dump App"
I/flutter ( 6559): ╎ ╚═══════════
I/flutter ( 6559): ╎
I/flutter ( 6559): └╌no offstage children
这是根RenderObject
对象的toStringDeep
函数的输出。
当调试布局问题时,关键要看的是size
和constraints
字段。约束沿着树向下传递,尺寸向上传递。
例如,在上面的转储中,您可以看到窗口大小,Size(411.4, 683.4),它用于强制RenderPositionedBox
下的所有渲染框到屏幕的大小,
约束条件为 BoxConstraints(w=411.4, h=683.4)。从RenderPositionedBox
的转储中看到是由Center widget创建的(如creator字段所描述的),
设置其孩子的约束为:BoxConstraints(0.0<=w<=411.4,0.0<=h<=683.4)。一个子widget RenderPadding进一步插入这些约束以添加填充空间,padding值为EdgeInsets(16.0, 0.0, 16.0, 0.0),因此RenderConstrainedBox具有约束BoxConstraints(0.0<=w<=395.4,
0.0<=h<=667.4)。该creator字段告诉我们的这个对象可能是其FlatButton
定义的一部分,它在其内容上设置最小宽度为88像素,并且设置高度为36.0像素(这是Material Design设计规范中FlatButton
类的尺寸标准)。
最内部RenderPositionedBox再次松开约束,这次是将按钮中的文本居中。
在RenderParagraph
中基于它的内容来决定其大小。
如果您现在按照size链继续往下查看,您会看到文本的大小是如何影响其按钮的框的宽度的,它们都是根据孩子的尺寸自行调整大小。
另一种需要注意的是每个盒子描述的”relayoutSubtreeRoot”部分,它告诉你有多少祖先以某种方式依赖于这个元素的大小。
因此,RenderParagraph
有一个relayoutSubtreeRoot=up8
,这意味着当它RenderParagraph
被标及为”dirty”时,它的八个祖先也必须被标记为”dirty”,因为它们可能受到新尺寸的影响。
如果您编写自己的渲染对象,则可以通过覆盖debugFillProperties()
将信息添加到转储。
将DiagnosticsProperty对象作为方法的参数,并调用父类方法。
层
如果您尝试调试合成问题,则可以使用debugDumpLayerTree()
。对于上面的例子,它会输出:
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
这是根Layer
的toStringDeep
输出的。
根部的变换是应用设备像素比的变换; 在这种情况下,每个逻辑像素代表3.5个设备像素。
RepaintBoundary
widget在渲染树的层中创建了一个RenderRepaintBoundary
。这用于减少需要重绘的需求量。
语义
您还可以调用debugDumpSemanticsTree()
获取语义树(呈现给系统可访问性API的树)的转储。
要使用此功能,必须首先启用辅助功能,例如启用系统辅助工具或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")
调度
要找出相对于帧的开始/结束事件发生的位置,可以切换debugPrintBeginFrameBanner
和debugPrintEndFrameBanner
布尔值以将帧的开始和结束打印到控制台。
例如:
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 : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
debugPrintScheduleFrameStacks
还可以用来打印导致当前帧被调度的调用堆栈。
可视化调试
您也可以通过设置debugPaintSizeEnabled
为true
以可视方式调试布局问题。
这是来自rendering
库的布尔值。它可以在任何时候启用,并在为true时影响绘制。
设置它的最简单方法是在void main()
的顶部设置。
当它被启用时,所有的盒子都会得到一个明亮的深青色边框,padding(来自widget如Padding)显示为浅蓝色,子widget周围有一个深蓝色框, 对齐方式(来自widget如Center和Align)显示为黄色箭头. 空白(如没有任何子节点的Container)以灰色显示。
debugPaintBaselinesEnabled
做了类似的事情,但对于具有基线的对象,文字基线以绿色显示,表意(ideographic)基线以橙色显示。
debugPaintPointersEnabled
标志打开一个特殊模式,任何正在点击的对象都会以深青色突出显示。
这可以帮助您确定某个对象是否以某种不正确地方式进行hit测试(Flutter检测点击的位置是否有能响应用户操作的widget),例如,如果它实际上超出了其父项的范围,首先不会考虑通过hit测试。
如果您尝试调试合成图层,例如以确定是否以及在何处添加RepaintBoundary
widget,则可以使用debugPaintLayerBordersEnabled
标志,
该标志用橙色或轮廓线标出每个层的边界,或者使用debugRepaintRainbowEnabled
标志,
只要他们重绘时,这会使该层被一组旋转色所覆盖。
所有这些标志只能在调试模式下工作。通常,Flutter框架中以“debug...
” 开头的任何内容都只能在调试模式下工作。
调试动画
调试动画最简单的方法是减慢它们的速度。为此,请将timeDilation
变量(在scheduler库中)设置为大于1.0的数字,例如50.0。
最好在应用程序启动时只设置一次。如果您在运行中更改它,尤其是在动画运行时将其值减小,则框架的观察时可能会倒退,这可能会导致断言并且通常会干扰您的工作。
调试性能问题
要了解您的应用程序导致重新布局或重新绘制的原因,您可以分别设置debugPrintMarkNeedsLayoutStacks
和
debugPrintMarkNeedsPaintStacks
标志。
每当渲染盒被要求重新布局和重新绘制时,这些都会将堆栈跟踪记录到控制台。如果这种方法对您有用,您可以使用services
库中的debugPrintStack()
方法按需打印堆栈痕迹。
衡量应用启动时间
要收集有关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
}
跟踪Dart代码性能
要执行自定义性能跟踪和测量Dart任意代码段的wall/CPU时间(类似于在Android上使用systrace)。
使用dart:developer
的Timeline工具来包含你想测试的代码块,例如:
Timeline.startSync('interesting function');
// iWonderHowLongThisTakes();
Timeline.finishSync();
然后打开你应用程序的Observatory timeline页面,在”Recorded Streams”中选择’Dart’复选框,并执行你想测量的功能。
刷新页面将在Chrome的跟踪工具中显示应用按时间顺序排列的timeline记录。
请确保运行flutter run
时带有--profile
标志,以确保运行时性能特征与您的最终产品差异最小。
Performance Overlay
要获得应用程序性能图,请将MaterialApp
构造函数的showPerformanceOverlay
参数设置为true。
WidgetsApp
构造函数也有类似的参数(如果你没有使用MaterialApp
或者WidgetsApp
,你可以通过将你的应用程序包装在一个stack中,
并将一个widget放在通过new PerformanceOverlay.allEnabled()
创建的stack上来获得相同的效果)。
这将显示两个图表。第一个是GPU线程花费的时间,最后一个是CPU线程花费的时间。 图中的白线以16ms增量沿纵轴显示; 如果图中超过这三条线之一,那么您的运行频率低于60Hz。横轴代表帧。 该图仅在应用程序绘制时更新,因此如果它处于空闲状态,该图将停止移动。
这应该始终在发布模式(release mode)下测试,因为在调试模式下,故意牺牲性能来换取有助于开发调试的功能,如assert声明,这些都是非常耗时的,因此结果将会产生误导。
Material grid
在开发实现Material Design的应用程序时,
将Material Design基线网格覆盖在应用程序上可能有助于验证对齐。
为此,MaterialApp
构造函数 有一个debugShowMaterialGrid
参数,
当在调试模式设置为true时,它将覆盖这样一个网格。
您也可以直接使用GridPaper
widget将这种网格覆盖在非Material应用程序上 。