iOS- Flutter App调试及异常捕获

90 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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_FocusScopeSemanticsFocus-[GlobalObjectKey MaterialPageRoute(560156430)]_ModalScope-[GlobalKey 328026813]_OverlayEntry-[GlobalKey 388965355]StackOverlay-[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可视化调试工具。