添加输出代码的方式调试 Flutter 应用
这篇文章描述了如何在代码中启用调试功能。如果想了解整个调试和分析工具,可参见 Debugging 页面.
日志输出
在应用中有两种日志输出方式。第一种方式是使用 stdout
和 stderr
。通常,我们使用 print()
语句或者通过引入 dart:io
并且调用 stderr
与 stdout
中的方法。如下:
stderr.writeln('print me');
如果您一次输出太多,Android 有时可能会丢失一些日志行。可以使用 Flutter 的 foundation
包中的 debugPrint()
方法来避免这个问题。它封装了 print
方法,通过控制输出的等级,从而避免输出内容被 Android 的内核丢弃。
另一种应用日志输出的方式是使用 dart:developer
中的 log()
方法。通过这种方式,您可以在输出日志中包含更精细化的信息。如下面这个示例:
import 'dart:developer' as developer;
void main() {
developer.log('log me', name: 'my.app.category');
developer.log('log me 1', name: 'my.other.category');
developer.log('log me 2', name: 'my.other.category');
}
您也可以在打印日志时传入应用数据。通常,在调用 log()
时也会使用命名参数 error:
,您可以通过 JSON 编码想要传入的对象,并将编码后的字符串传给 error 参数。
import 'dart:convert';
import 'dart:developer' as developer;
void main() {
var myCustomObject = MyCustomObject();
developer.log(
'log me',
name: 'my.app.category',
error: jsonEncode(myCustomObject),
);
}
如果在 DevTool 的 logging 页面中查看日志输出情况, JSON 编码的错误参数会被解释为一个数据对象,并呈现在该日志条目的 details 视图中。
设置断点
您可以使用 debugger()
语句插入编程式断点。在此之前,您需要在相关文件顶部引入 dart:developer
包。
debugger()
语句有一个可选参数 when
,用来指定该断点触发的特定条件,如下这个示例:
import 'dart:developer';
void someFunction(double offset) {
debugger(when: offset > 30);
// ...
}
Debug 标识:应用程序层
Flutter 框架的每个 layer 都提供了一个函数,用来将其当前状态或事件转储到控制台(使用 debugPrint
)。
Widget 树
可以通过调用 debugDumpApp()
方法转储 widget 库的状态,如果应用已至少构建了一次,并且正处于调试模式时(runApp()
调用后的任何时间)。只要应用不在运行构建阶段,您可以调用随意该方法(也就是说,不能在 build()
方法中使用它)。
如下面这个应用:
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: AppHome(),
),
);
}
class AppHome extends StatelessWidget {
const AppHome({super.key});
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: TextButton(
onPressed: () {
debugDumpApp();
},
child: const 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<void>(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<void>(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<void>(/))➩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<void>(/))➩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): └TextButton()
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)
这是一个「被拉平的树」,通过它们的各种 build 函数,显示出所有 widget 信息。(如果您调用根 widget 的 toStringDeep()
方法,就会得到这棵树。)您会看到很多 widget ,虽然它们没出现在应用的源码中,但却出现在这颗树中,因为它们是由框架中 widget 的 build 函数插入的。比如,Material
widget 的实现细节中就包括了 InkFeature
。
当按钮被点击响应时,debugDumpApp()
方法被调用,由于该方法与 TextButton
对象调用 setState()
相一致,因此 TextButton 对应的元素会被标记为 dirty。这就是为什么在查看转储信息时,您会看到被标记为「dirty」的特定对象。您也可以看到已经被注册的手势监听器;在这个案例中,列出了一个 GestureDetector,它只监听「tap」手势(这里「tap」是 TapGestureDetector
的 toStringShort
函数输出的)。
对于您自定义的 widget,可以通过重写 debugFillProperties()
方法添加信息。为方法中的参数添加 DiagnosticsProperty 对象,并调用父类方法。该方法在 widget 调用 toString
方法时会被填充到其描述信息中。
Render 树
如果您试图调试一个布局问题,那么 Widget 层的树可能不够详细。在这种情况下,您可以通过调用 debugDumpRenderTree()
转储 Render 树信息。和 debugDumpApp()
一样,除了在布局或绘制阶段,可以在任何时候调用它。一般来说,最好在 frame callback 或事件处理中调用它。
想要调用 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<void>(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<void>(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<void>(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<void>(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<void>(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 ← TextButton ← 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): ╎ │ TextButton ← 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): ╎ │ TextButton ← 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
两个字段。
constraint 沿树向下传递,而 size 则向上追溯。
比如,从上面转储信息中可以看出窗口尺寸是 Size(411.4, 683.4)
,它用于强制 RenderPositionedBox
之前的所有 box 为屏幕尺寸,其约束为 BoxConstraints(w=411.4, h=683.4)
。从转储文件可以看出 RenderPositionedBox
是由 Center
widget 创建的(可以从 creator
字段的描述看出来),并将其 child 的约束条件变得松散:约束范围是 BoxConstraints(0.0<=w<=411.4, 0.0<=h<=683.4)
。其后代的 RenderPadding
进一步插入这些约束来确保留出空间作为内边距,因此 RenderConstrainedBox
有一个宽松的约束,该约束为:
BoxConstraints(0.0<=w<=395.4,0.0<=h<=667.4)
。
creator
字段告诉我们,这个对象很可能是 TextButton
定义的一部分,它内容的最小宽度为 88 像素,具体高度为 36.0。(TextButton
是 Material Design 中按钮尺寸标准的实现。)
最内部的 RenderPositionedBox
再次放松了约束,这次是把文本放在了按钮的中间。
RenderParagraph
可以根据其内容确定自身大小。如果您现在沿着这条链路往回追溯渲染对象的尺寸大小,您就会看到在文本的大小是如何影响按钮边框大小的形成过程,因为它们都会根据子组件的尺寸自行调整大小。
注意到这点的另一种方式为:查看每个 box 的「relayoutSubtreeRoot」部分,它本质上在告诉您,在某种程度上有多少祖先在依赖于这个元素的尺寸。因此,RenderParagraph
有 relayoutSubtreeRoot=up8
,这意味着当 RenderParagraph
被标为 dirty 时,8 个祖先也会被标为 dirty,因为它们可能会受到新尺寸的影响。
对于您自己写的 render 对象,可以通过重写 debugFillProperties()
方法为转储数据添加信息。在方法中的参数中添加 DiagnosticsProperty 对象,并调用父类方法即可。
Layer 树
如果您在尝试调试一个合成问题,您可以使用 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
方法时的输出结果。
根结点的 transform 是设备像素比率的变换;在该示例中,每个逻辑像素对应 3.5 个设备像素。
RepaintBoundary
widget 在 render 树中创建了一个 RenderRepaintBoundary
,并在 layer 树中创建了一个新的层。这可以用来减少需要重绘的次数。
Focus 树
要调试焦点或快捷键问题,可以使用 debugDumpFocusTree()
方法转储 focus 树。
例如:
I/flutter : FocusManager#6fb59
I/flutter : │ primaryFocus: FocusScopeNode#3c26f(_ModalScopeState<dynamic>
I/flutter : │ Focus Scope [PRIMARY FOCUS])
I/flutter : │ primaryFocusCreator: FocusScope ← PrimaryScrollController ←
I/flutter : │ _ActionsScope ← Actions ← Builder ← PageStorage ← Offstage ←
I/flutter : │ _ModalScopeStatus ← UnmanagedRestorationScope ←
I/flutter : │ RestorationScope ← AnimatedBuilder ←
I/flutter : │ _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#f36a2]
I/flutter : │ ← Semantics ← _RenderTheaterMarker ← _EffectiveTickerMode ←
I/flutter : │ TickerMode ←
I/flutter : │ _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#2e2a3]
I/flutter : │ ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#89fc1] ←
I/flutter : │ UnmanagedRestorationScope ← ⋯
I/flutter : │
I/flutter : └─rootScope: FocusScopeNode#95ff1(Root Focus Scope [IN FOCUS PATH])
I/flutter : │ IN FOCUS PATH
I/flutter : │ focusedChildren: FocusScopeNode#001cc(Navigator Scope [IN FOCUS
I/flutter : │ PATH])
I/flutter : │
I/flutter : └─Child 1: FocusNode#79786([IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusNode#15aec(Shortcuts [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusNode#3514b(Shortcuts [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: _FocusTraversalGroupNode#0ccda(FocusTraversalGroup [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusNode#e2413(Shortcuts [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusScopeNode#001cc(Navigator Scope [IN FOCUS PATH])
I/flutter : │ context: FocusScope
I/flutter : │ IN FOCUS PATH
I/flutter : │ focusedChildren: FocusScopeNode#3c26f(_ModalScopeState<dynamic>
I/flutter : │ Focus Scope [PRIMARY FOCUS])
I/flutter : │
I/flutter : └─Child 1: _FocusTraversalGroupNode#1d456(FocusTraversalGroup [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ NOT FOCUSABLE
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusNode#3635f(Navigator [IN FOCUS PATH])
I/flutter : │ context: Focus
I/flutter : │ IN FOCUS PATH
I/flutter : │
I/flutter : └─Child 1: FocusScopeNode#3c26f(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])
I/flutter : context: FocusScope
I/flutter : PRIMARY FOCUS
聚焦的节点标记为 PRIMARY FOCUS
。聚焦节点的祖先标记为 IN FOCUS PATH
。
如果您的应用使用 Focus
widget,可以使用 debugLabel
属性来更容易地在树中找到它的 focus 节点。
你也可以使用布尔类型标记 debugFocusChanges
在 focus 改变时启用详细的日志记录。
Semantics 树
您也可以使用 debugDumpSemanticsTree()
获得 Semantics 树(该树提供了系统的 accessibility API)的转储信息。想要使用它,首先必须启用 accessibility,例如,通过启用系统 accessibility 工具或 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")
Scheduling
如果您想要找到事件触发对应的开始或结束帧,可以将 debugPrintBeginFrameBanner
和 debugPrintEndFrameBanner
这两个布尔值切换为 true,在控制台中打印开始和结束帧的信息。
比如:
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()
中设置它,如下案例代码所示:
//add import to rendering library
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(const MyApp());
}
当它被启用时,所有的 box 都会有明亮的蓝绿色边框,内边距(来自于 widgets,比如 Padding
)显示为淡蓝色,并在 child 周围有一个深蓝色的 box,对齐方式(来自于 widgets,比如 Center
和 Align
)显示为黄色箭头,还有间隔(来自于 widgets,比如当 Container
没有 child 时)显示灰色。
debugPaintBaselinesEnabled
][] 标志和它类似,但只针对于带有基线的对象。
alphabetic 基线用亮绿色显示,ideographic 基线用橙色显示。
debugPaintPointersEnabled
标志会打开一个特殊模式,任何被选中的对象都会以蓝绿色高亮显示。这可以帮助您确定对象是否会以某种方式未能正确命中测试(这是可能会发生的,例如,实际上它在父节点的边界之外,因此一开始就不用考虑进行命中测试)。
如果您试图调试合成层,比如要确定是否应该在某处添加 RepaintBoundary
widget,您可以使用 debugPaintLayerBordersEnabled
标志,来用为每个 layer 的边界显示橙色边框,或使用 debugRepaintRainbowEnabled
标志,这会使得每当重新绘制图层时,边框的颜色就会被一组轮转的颜色覆盖。
上面所有的标志都只在 调试模式 下生效。一般来说,Flutter
框架中以「debug...
」开头的都只能在调试模式下工作。
调试动画
将 timeDilation
变量(来自 scheduler
库)设置为大于 1.0 的数字,例如,50.0。该操作最好在应用启动时只执行一次。如果您动态地改变,尤其是在动画运行时减少它时,框架可能会观察到时间倒退,这可能会导致断言失败,通常这会让您徒劳无功。
调试标志:性能
Flutter 提供了各种各样的调试标志和功能,来帮助您在开发周期的不同阶段调试应用。想要使用这些特性,必须在调试模式下编译。下面的列表虽然不完整,但是突出显示了 rendering library 中用于调试性能问题的一些标志(以及一个函数)。
您可以通过修改框架的代码来设置这些标志,或者将模块导入,并在 main()
方法中设置标志值,然后热重启。
debugDumpRenderTree()
-
当不在布局或重新绘制阶段时,调用此函数将 render 树转储到控制台。(可以从
flutter run
按下 t 调用此命令。)通过搜索其中的「RepaintBoundary」可以查看关于边界的有用诊断信息。 debugPaintLayerBordersEnabled
-
PENDING
debugRepaintRainbowEnabled
-
您可以通过点击 Highlight Repaints 按钮,在 Flutter inspector 中启用此标志。如果任何静态 widget 在彩虹七颜色之间轮转(比如一个静态标题),那么这些区域就可能需要添加重新绘制边界进行优化。
debugPrintMarkNeedsLayoutStacks
-
如果您看到的布局比预期的要多(比如,在 timeline 、profile 或者一个布局方法中的
print
语句中),可以启用这个标志。一旦启用,控制台将会充满堆栈跟踪,来显示在布局时每个渲染对象被标记为 dirty 的原因。如果有需要的话,您可以使用services
库中的debugPrintStack()
方法按需打印出堆栈的跟踪信息。 debugPrintMarkNeedsPaintStacks
-
它和
debugPrintMarkNeedsLayoutStacks
类似,但用于多余的绘制。如果有需要的话,您可以使用services
库中的debugPrintStack()
方法按需打印出堆栈的跟踪信息。想要以编程方式执行自定义性能跟踪和测量任意代码片段的 wall/CPU 时间,这类似于在 Android 上使用 systrace,您可以使用
dart:developer
包中的 Timeline 类提供的一些静态方法包裹您想测量的代码,比如:import 'dart:developer'; void main() { Timeline.startSync('interesting function'); // iWonderHowLongThisTakes(); Timeline.finishSync(); }
当链接到你的应用上之后,打开发者工具的 Timeline 事件图。确保在 Performance 设置上勾选了 Dart 记录的选项,并执行您想要测量的方法。
确保以 性能模式 运行您的应用,来确保运行时的性能表现与您的最终产品相近。
您可以通过编程方式启用 PerformanceOverlay widget,在
MaterialApp
、CupertinoApp
或WidgetsApp
构造函数中,将showPerformanceOverlay
属性设置为true
即可。import 'package:flutter/material.dart'; class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( showPerformanceOverlay: true, title: 'My Awesome App', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'My Awesome App'), ); } }
(如果您没有使用
MaterialApp
、CupertinoApp
或WidgetsApp
,可以通过将应用包装在一个 Stack 中,并通过调用PerformanceOverlay.allEnabled()
来创建一个 widget,来获得相同的效果。)有关如何解释浮层中的图形的信息,可以参见 Flutter 性能分析 中的 性能图层。
您可以通过编程的方式将 Material Design 基线网格 覆盖在应用的顶层来辅助对齐校验,通过使用
MaterialApp
构造函数 中的debugShowMaterialGrid
参数进行设置。在非 Material 应用中,您可以通过直接使用
GridPaper
widget 来达到类似的效果。