开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情
日志打印
- debugger()
声明
当使用Dart Observatory时,可以使用该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)。但是这个有一定限制,如果数据太长,那么会被丢弃部分数据,和iOS的打印差不多。为了避免这个情况,可以使用Flutter的 foundation库中的debugPrint()。这个封装的print,它将输出限制在一个级别,避免被内核丢弃。
Flutter框架中许多类都有toString实现。这些输出通常包括对象的runtimeType单行输出,通常在表单中ClassName(more information abput this instance...)。树中使用的一些类也具有toStringDeep,从该点返回整个子树的多行描述。已一些具有详细信息toString的类会实现一个toStringShort,它只返回对象类型或其他非常简短的描述。
断点
开发过程中,断点是最常用也是最实用的调试工具之一,和Xcode不同的是Android Studio的断点必须在调试模式下才可进行,而Xcode是在任何环境下都可进行断点。Android Studio断点是需要点击运行按钮Run旁边的Debug Run按钮,也就是一个瓢虫按钮。运行后,其使用方式和Xcode差不多,在运行到断点处,会在控制台显示上下文的一些信息,便于我们找到问题。
调试应用程序层
Flutter框架的每一层都提供了将其当前状态或事件转储(dump)到控制台(使用debugPrint())的功能。 要转储Widgets树的状态,需要调用debugDumpApp()函数,只需要应用程序已经构建了至少一次(build之后的任何时间),调用次方法。举例:
import 'package:flutter/material.dart';
class AppHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: TextButton(
onPressed: () {
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))
通过这个输出的树状信息去分析所有的Widget,其中有些是我们没有使用过的,那是系统框架插入的。
渲染树
如果我们尝试调试布局问题,那么widget可能不够详细。在这种情况,我们可以通过调用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)
这是根RenderObject对象的toStringDeep函数的输出。
当调试布局问题时,关键要看的是size和constraints字段。约束沿着树向下传递,尺寸向上传递。如果我们编写自己的渲染对象,则可以通过覆盖debugFillProperties()将信息添加到转储。将DiagnosticsProperty对象作为方法的参数,并调用父类方法。
Layer树
可以理解为渲染树是可以分层的,而最终绘制需要将不同的层合成起来,而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输出的。
根部的变换是应用设备像素比的变换;在这种情况下,每个逻辑像素代表3.5个设备像素。 RepaintBoundary widget在渲染树的层中创建了一个RenderRepaintBoundary。这用于减少需要重绘的需求量。
语义
我们可以调用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")
调度
要找出相对于帧的开始/结束事件发生的位置,可以切换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周围有一个深蓝色框,对齐方式显示为黄色箭头.空白以灰色显示。
debugPaintBaselinesEnabled
(opens new window)做了类似的事情,但对于具有基线的对象,文字基线以绿色显示,表意(ideographic)基线以橙色显示。
debugPaintPointersEnabled
(opens new window)标志打开一个特殊模式,任何正在点击的对象都会以深青色突出显示。 这可以帮助我们确定某个对象是否以某种不正确的方式进行hit测试(Flutter检测点击的位置是否有能响应用户操作的widget),例如,如果它实际上超出了其父项的范围,首先不会考虑通过hit测试。
如果我们尝试调试合成图层,例如以确定是否以及在何处添加RepaintBoundary
widget,则可以使用debugPaintLayerBordersEnabled
(opens new window)标志, 该标志用橙色或轮廓线标出每个层的边界,或者使用debugRepaintRainbowEnabled
(opens new window)标志, 只要他们重绘时,这会使该层被一组旋转色所覆盖。
所有这些标志只能在调试模式下工作。通常,Flutter框架中以“debug...
” 开头的任何内容都只能在调试模式下工作.
上面是一些主要是介绍一些模式下,Flutter会做哪些操作,然后看了后并不知道该怎么调试,那么就看下面这个帖子.其他的一些性能调试,在这个帖子上都会有一些描述,讲解了如果使用DevTools可视化调试工具。