Flutter App 软件调试指南

本场 Chat 是《深入 Flutter 系列课程》第三讲,主要聊聊如何进行 Flutter App 代码的调试。

通过本场 Chat,您将获得以下技能:

  1. 认识 Dart 语言检查器;
  2. 如何在 IDE 中进行单步调试;
  3. 打印 Log 的技巧;
  4. 利用 Dart 语言中的“断言”;
  5. 如何查看界面 Widget 树形层级;
  6. 怎样获取语义树。

本场 Chat 依然会结合具体的代码展开讨论,欢迎大家预定关注,谢谢!

#

前言

在实际开发中,测试和调试所占的时间比例,在总开发时间中还是比较高的。在修复产品缺陷时,我们通常需要实时观察某个对象的值。虽然可以通过Log的形式进行输出,但在某些情形下,使用更好的调试工具可以使观察这些值变得更加方便。
想象一下,如果需要观察一个集合,或者一个对象中所有变量的值,单纯地使用Log需要怎么做br>可能会想到用循环,也可能会在输出Log的代码中多次运用“.”运算符对对象内的变量取值。这使得编写Log输出语句本身变得复杂,再加上可能还会冒着空指针的风险。

  1. 认识 Dart 语言检查器;
  2. 如何在 IDE 中进行单步调试;
  3. 打印 Log 的技巧;
  4. 利用 Dart 语言中的“断言”;
  5. 如何查看界面 Widget 树形层级;
  6. 怎样获取语义树。

下面我们来逐一进行学习。

认识 Dart 语言检查器

在运行应用程序前,使用Dart语言检查器,通过分析代码,可以帮助开发者排除一些代码隐患。
当然,如果读者使用的是Android Studio,Dart检查器在默认情况下会自动启用。
若要手动测试代码,可以在工程根目录下执行:

命令,检查结果会稍后显示在命令行对话框中。比如,在默认新建的计数器应用中,去掉一个语句结尾的分 :

看到_counter++后面少了一个分 了吗br>此时,运行Dart检查器,命令行输出:

如何在 IDE 中进行单步调试

在某些时候,我们需要进行单步调试。单步调试可以让程序逐条语句地进行,并可以看到当前运行的位置。另外,在单步调试过程中,还能实时关注相应范围内所有变量值的详细变化过程。

Android Studio中提供了单步调试功能。这和开发原生Android平台App时的单步调试方法一样,其具体步骤可以分为三步进行,第一步是标记断点,第二步是运行程序到断点处,第三步则是使用Debug工具进行调试。
下面以默认的计数器应用为例,观察代码中_counter值的变化,体会单步调试的全过程。

第一步是标记断点,既然要观察_counter值的变化,则在每次_counter值发生变化后添加断点,观察数值变化是最理想的,因此在行 稍右侧点击鼠标,把断点加载下图所示的位置。

稍等片刻,程序就启动了。由于我们添加断点的位置在程序启动后会被立即运行到,因此,无需其他操作,即可进入调试视图。如果断点位置并不是在程序一启动就执行,则需要手动让程序运行到断点位置。
下图展示了代码运行到断点位置时的IDE视图,它自动进入了Debug视图模式:

在相应的变量上点右键,接着在弹出的菜单中选择计算表达式(Evaluate Expression),最后在弹出的对话框中点击Evaluate按钮,得到运算结果如下图所示:

接下来,保持App继续运行,然后点击界面右下角的FloatingActionButton,验证一下点击后_counter值的准确性。此时,需要点击“运行到下一个断点处”按钮,如下图所示:

要结束监视Log输出,可使用Control + C组合键,然后输入y,回车确认,也可直接关闭控制台。最后,需要注意的是,为了保证Log输出正确无误,建议各位读者使用英文输出,而不是直接使用中文。因为在某些情况下,可能会导致显示乱码。经测试,在英文版的Windows下启动命令提示符,并执行上例,会得到如下输出:

利用 Dart 语言中的“断言”;

Dart运行时提供两种运行方式:Production和Checked。默认情况下会以Production模式运行,在这种条件下,优先考虑性能,关闭类型检查和断言;反之,Checked模式更利于开发阶段调试使用。
断言可以检查程序中某些可能出现的运行时逻辑错误。比如下面这段代码:

很明显,intValue不满足和299相等的条件,此时在开发环境中运行程序,将看到控制台 错。而一旦切换到生产模式,则不会收到任何错误提示。这对于检查代码中某些隐含的逻辑问题十分有效。

如何查看界面 Widget 树形层级;

Flutter框架中的每一层都提供了转储当前状态或事件的方式,这些转储的信息将通过debugPrint()方式输出到控制台上。
下面就让我们逐层探究,了解其Log日志转储的方法和内容。

组件层

要转储组件层的状态,需要调用debugdumpapp()方法。
保证该方法取到有效Log的前提是要确保该App至少构建了一次组件,且不要在build()过程中调用。
我们以新建的计数器应用为例,添加一个创建组件层日志转储的按钮,并在用户点击该按钮后,执行debugdumpapp()方法。具体实现如下:

注意新添加的RaisedButton。
运行上述代码,然后点击该按钮,可以看到控制台如下输出(节选):

实际的输出内容要比上述节选部分多好几倍。

这些内容乍看上去似乎很复杂的样子,但仔细观察后发现:组件层的转储信息实际上就是把所有的组件按照树形结构罗列了出来。其中包含了组件的样式、值等信息。当然,还会看到某些未曾在代码中体现的组件。这是因为这些组件在框架本身的组件中有所使用。比如RaisedButton中的InkWell,虽然没有通过代码实现InkWell,但RaisedButton本身为了实现相应的效果,在其中使用了InkWell组件。

此外,在转储信息中,会有某个组件被标记为dirty,这是因为创建转储信息的行为是通过该组件触发的。本例中,被标记为dirty的组件如下:

可见,它就是为了执行debugDumpApp()方法而增加的按钮。

渲染层

由上一小节得知组件层提供了各个组件的详情信息。但某些时候,这些信息并不完全够使用,此时可以调用debugDumpRenderTree()方法转储渲染层。
基于上小节的示例,继续添加一个按钮,其操作就是触发debugDumpRenderTree()方法。如下:

程序运行后,单击这个按钮,观察控制台输出(节选):

这段节选依然比实际输出少很多。
不过,这些转储信息,通常只关注size和constrains参数就可以了。因为它们表示了大小和约束条件。此外,针对盒约束,还有可能存在relayoutSubtreeRoot,它表示有多少父控件依赖该组件的尺寸。

层的合成

如果要调试有关合成的问题,就需要转储层级关系的信息。
转储层级关系的方法是debugDumpLayerTree()。我们继续添加一个按钮,其操作就是触发debugDumpLayerTree()方法。如下:

运行,并点击该按钮,得到控制台输出:

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年6月24日
下一篇 2019年6月25日

相关推荐