本文转载于 SegmentFault 社区

作者:xiangzhihong


程序调试是程序投入运行之前,使用手工或编译程序等方法进行的测试,主要用以修正语法错误和逻辑错误。程序调试是保证计算机信息系统正确性的必不可少的步骤。
在 Flutter 应用开发中,Android Studio 和 VSCode 是两种比较常见的集成开发环境,因此项目调试也围绕这两款 IDE 进行。Android Studio 为 Flutter 提供完整的集成 IDE 体验,因此 Android 的调试技巧对于 Flutter 来说也是适用的。在 Flutter 中,调试代码主要分为输出日志、断点调试和布局调试 3 类,因此 Flutter 的调试也将围绕这 3 个主题。

Android Studio

输出日志

为了便于跟踪和记录应用的运行情况,我们在开发时通常会在一些关键步骤输出日志 (Log) ,在 Flutter 中我们使用 print 函数在控制台打印出相关的上下文信息。通过这些信息,就可以定位代码中可能出现的问题。不过,由于涉及 I/O 操作,使用 print 来打印信息会消耗较多的系统资源。同时,这些输出数据很可能会暴露 App 的执行细节,所以我们在发布正式版时还需要屏蔽掉这些输出。 不过最工程化的做法是读取项目配置文件,根据运行环境来开启日志调试功能。为了根据不同的运行环境来开启日志调试功能,我们可以使用 Flutter 提供的 debugPrint 来代替 print。debugPrint 函数同样会将消息打印至控制台,但与 print 不同的是,它提供了定制打印的能力。也就是说,我们可以向 debugPrint 函数,赋值一个函数声明来自定义打印行为。 比如在下面的代码中,我们将 debugPrint 函数定义为一个空函数体,这样就可以实现一键取消打印的功能了。

debugPrint = (String message, {int wrapWidth}) {};//空实现
在 Flutter 中,我们可以使用不同的 main 文件来表示不同环境下的入口。同样,在 Flutter 开发中,可以通过 main.dart 与 main-dev.dart,去分别定义生产环境与开发环境不同的打印日志行为。 在下面的例子中,我们将生产环境的 debugPrint 定义为空实现,将开发环境的 debugPrint 定义为同步打印数据,如下所示。

//main.dart
void main() {
// 将debugPrint指定为空的执行体, 所以它什么也不做
debugPrint = (String message, {int wrapWidth}) {};
runApp(MyApp());
}

//main-dev.dart
void main() async {
// 将debugPrint指定为同步打印数据
debugPrint = (String message, {int wrapWidth}) => debugPrintSynchronously(message, wrapWidth: wrapWidth);
runApp(MyApp());
}
可以看到,在代码实现上,我们只要将应用内所有的 print 都替换成 debugPrint,就可以满足开发环境下打日志的需求,也可以保证生产环境下应用的执行信息不会被意外打印。

断点调试

输出日志固然方便,但如果要想获取更为详细,或是粒度更细的上下文信息,静态调试的方式非常不方便。这时,我们需要更为灵活的动态调试方法,即断点调试。断点调试可以让代码在目标语句上暂停,让程序逐条执行后续的代码语句,来帮助我们实时关注代码执行上下文中所有变量值的详细变化过程。 Android Studio 提供了断点调试的功能,调试 Flutter 应用与调试原生 Android 代码的方法完全一样,具体可以分为三步,即标记断点、调试应用、查看信息。 下面以 Flutter 默认的计数器应用模板为例,观察代码中 _counter 值的变化,体会断点调试的全过程。 首先是标记断点。既然我们要观察 _counter 值的变化,因此在界面上展示最新的 _counter 值时添加断点,去观察其数值变化是最理想的。因此,我们在行号右侧点击鼠标,可以把断点加载到初始化 Text 控件所示的位置。 在下图的例子中,我们为了观察 _counter 在等于 20 的时候是否正常,还特意设置了一个条件断点 _counter==20,这样调试器就只会在第 20 次点击计数器按钮时暂停下来。 47b63ad6ebb084e4ac9093e93952f098.png 添加断点后,对应的行号将会出现圆形的断点标记,并高亮显示整行代码。到此,断点就添加好了。当然,我们还可以同时添加多个断点,以便更好地观察代码的执行过程。 接下来则是调试应用了。和之前通过点击 Run 按钮的运行方式不同,这一次我们需要点击工具栏上的虫子图标,以调试模式启动 App,如下图所示。 6f112bed65907b0315c666b2774ec419.png 等调试器初始化好后,我们的程序就启动了。由于设置了断点,所以当代码运行到了断点位置,自动进入了 Debug 视图模式,如下图所示。 12c65477a2cc79fc5200010f7e44d127.png   按照功能的不同,可以把 Debug 视图模式划分为 4 个区域,即 A 区控制调试工具、B 区步进调试工具、C 区帧调试窗口、D 区变量查看窗口。 控制调试工具区域主要用来控制调试的执行情况,如下图所示。 b64bbbec7e8091e06b6bc5ee8c41cd95.png 我们可以点击继续执行按钮来让程序继续运行、点击终止执行按钮来让程序终止运行、点击重新执行按钮来让程序重新启动,或是在程序正常执行时,点击暂停执行按钮按钮来让程序暂停运行。当然,我们可以点击编辑断点按钮来编辑断点信息,或是点击禁用断点按钮来取消断点。
步进调试工具区域主要用来控制断点的步进情况,如下图所示。 a6697f740d7b9cf12618b38ec92e4aaf.png 可以点击单步跳过按钮来让程序单步执行 (但不会进入方法体内部) 、点击单步进入或强制单步进入按钮让程序逐条语句执行,甚至还可以点击运行到光标处按钮让程序执行到在光标处。认为断点所在的方法体已经无需执行时,则可以点击单步跳出按钮让程序立刻执行完当前进入的方法,从而返回方法调用处的下一行。担任,还可以点击表达式计算按钮来通过赋值或表达式方式修改任意变量的值。 C 区用来指示当前断点所包含的函数执行堆栈,D 区则是其堆栈中的函数帧所对应的变量。

布局调试

除了输出日志、断点调试,布局分析也是开发中不可缺少的代码优化手段。借助 Flutter提供的 Flutter Inspector 可视化工具,可以帮助我们诊断布局问题。打开 Android Studio,然后点击工具栏上的“Open DevTools”按钮即可启动 Flutter Inspector,如下图所示。 2450417d330115d88e33139c4d4d52c7.png 随后,Android Studio 会打开浏览器,将 Flutter应用程序的的 Widget 树结构展示在面板中。可以看到,Flutter Inspector 所展示的 Widget 树结构,与代码中实现的 Widget 层次是一一对应的。 ebc3fba487fee7de4ce35a25bdf815d0.png   除了进行布局调试外,还可以使用 Flutter Inspector 进行布局调优。

VSCode

除了 Android Studio 外,VSCode 也是一款比较常见的 Flutter 应用程序开发工具。使用 VSCode 提供的图形化调试界面,开发者可以很方便的进行 Flutter 应用的调试工作。使用 VSCode 打开 Flutter 项目,然后点击 VSCode 的断点调试按钮即可开启调试,如下图所示。 de80eabbc60bcb5b83814e21ed9619d7.png 需要说明的是,第一次使用 VSCode 进行断点调试时,需要先安装并激活 Dart DevTools 调试工具。如果不确定是否绑定过 DevTools 工具,可以使用快捷键【command+shift+p】打开 VSCode 工具栏,然后输入 Open DevTools 打开调试窗口。
然后,在需要调试的代码处设置断点,点击左上方的开启调试按钮开启调试即可。当代码运行到断点处时,就会停留在断点处,然后就可以进行相关调试操作。
- END - 4b777593fd1d527be84409b647425dbb.png
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐