第3章 调试Flutter应用程序 在开发任何类型的App时,调试是一定要做的事情。通过调试我们可以定位App的错误。本章我们将学习在开发App时遇到的各种问题和解决方法。让我们开始吧! 3.1解决语法错误 我们以一个简单的问题或经常出现的语法错误开始。什么是语法错误?语法错误表示编写的代码无法工作。 视频讲解 例如: 调用某个对象不存在的方法、忘记添加import引入等。 在news_manager.dart文件中注释掉import这行代码,代码如下: // import 'package:flutter/material.dart';//注释引入的包 IDE中报了语法错误,如图3.1所示。 图3.1错误提示信息 IDE能检测大多数的语法错误,把鼠标悬停在红线处,被告知“没有定义StatefulWidget,方法也没有在superclass中定义”。Dart语言中任何值都是对象,数字是对象,列表是对象,它们都继承于一个基类Superclass,即使自己定义的类继承了其他的类,它的上级的类终将继承这个基类,这就是这里报错的原因。 news_manager.dart很多错都是报“没有定义方法”。这意味着我们没有定义类或者忘记引入类,所以语法错误通常是逻辑上出的问题。语法错误不仅仅是漏掉import。如果把news_manager.dart中的属性_news前面的下画线去掉,就会出现如图3.2所示的错误提示信息。 图3.2属性的错误提示信息 错误提示没有定义_news,所以出现这些情况的时候,首先要做的是检查代码。代码中是否添加了这个属性?忘记引入什么了吗? 还有一些错误是忘记编写了一些内容,例如分号,这是典型的语法错误。还有一种语法错误是赋值类型与属性类型不符,例如需要传入的是浮点型数据,但是如果传入了整型数据,会有错误提示“整型不能赋值给浮点型”。 IDE可以提供给我们很多警告或者是错误信息,如果阅读这些错误信息就可以很好地解决语法错误。 视频讲解 3.2运行时错误和运行时日志消息 在模拟器中多次单击添加资讯的按钮,如图3.3所示。 图3.3模拟器 发现如图3.4所示,显示了一些错误信息。这是Flutter的一个很好的功能。 图3.4显示的错误信息 在Debug Console中可以看到一个错误信息。那样怎样阅读Flutter的错误信息呢?首先检查底部,有时可以在底部找到重要的信息。Debug Console中打印了很多信息,需要向上滚动,找到如图3.5所示的分隔线。 图3.5Debug Console中的分隔线 在分隔线下面可以发现“底部的渲染超过了174像素”,这部分信息很有用,Flutter中的错误信息通常提供解决办法或者提供一个间接的解决方案,例如这里可以看到详细的问题描述,建议使用flex factor,或者可以使用ListView。这些建议很有用。当遇到错误信息时,要认真阅读它们,很多错误信息描述得都很清晰,甚至提供了解决方法,没有解决方法的也会告诉哪里错了。 看另外一个例子,在属性_news的值前面加上static const,编译没有报错,但如果重启应用会看到错误信息,屏幕上也有错误信息,显示不能给一个不可以改变的列表添加值。在Debug Console中检查时会发现这样的提示: unsupported operation cannot add to an unmodified list.这些错误叫运行时错误,它们不会在开发的时候报错。运行时错误通常描述得很清晰,显示错误是在文件中的哪个地方发生的,同时也可以通过跟踪问题栈直接找到有用的错误信息。 视频讲解 3.3处理逻辑错误 现在学习一下如何处理逻辑错误。什么是逻辑错误呢?在news_manager.dart中的_addNews()方法中,可能由于一时疏忽,忘记调用setState()方法。 这样的错误不会产生提示,因为是忘记了添加内容。保存并重启应用,没有任何错误提示,但如果单击添加资讯按钮,什么事情都没有发生,这时我们意识到可能是哪里出错了,然而却没有任何错误提示。应用并没有按照我们想要的效果显示,这就是逻辑错误。也是最难发现的错误,因为App运行正常,而且没有错误提示。 如何调试这样的逻辑错误呢?一种方法是查看代码,我们自己最清楚单击按钮时应该发生什么,所以首先检查单击按钮是否调用了正确的方法,同时检查是否传递了正确的数据,然而并没有发现问题。 再检查_addNews()方法,快速核对一下这个_news列表是否更新了,可以打印一条语句,代码如下: // Chapter03/03-03/lib/news_manager.dart void _addNews(String news) {  // 添加资讯方法 _news.add(news);   // 给_news列表添加数据 print(_news);   // 打印_news列表中的数据 } print()方法不会在模拟器上显示任何内容,所以我们把print()方法叫作调试工具。保存重新加载后,在控制台中显示的内容一切正常,所以问题不在这里。这时应该想到是不是Flutter没有意识到这里的改变?我们在这里添加setState()方法,解决了这个问题。这种代码跟踪的方式可以解决像这样的逻辑错误。但有些时候可能会很难,因为代码可能会很复杂,所以下一节我们学习如何使用断点来调试。 视频讲解 3.4使用debug断点调试 3.3节的代码跟踪不需要设置断点,很多IDE都提供debug工具,如图3.6所示。 图3.6IDE中的断点 在代码所在行的左侧单击后有一个小红点,这就是断点。使用断点需要用debug方式启动App,启动好后,单击添加资讯按钮会跳转到IDE界面,停在我们标记的这一行上,这时可以使用顶部的控制面板来跟踪代码,如图3.7所示, 图3.7IDE中的调试面板 单击向下箭头,会进入_addNews()方法中,继续单击可以跟踪代码执行的每一步。把鼠标悬停在参数上面,如图3.8所示,可以能看到它内部包含哪些内容。 图3.8显示当前news中包含的数据等信息 在IDE的watch区域,输入news,继续运行代码,可以观察news的变化。我们还可以通过IDE的调用栈调试,观察传入的参数是否正确,或者变量中是否保存了一个非期望的值。使用debug工具和断点绝对是个好办法。如果完成了调试可以单击图3.7中最左侧的三角形箭头来执行后面的代码。如果想删除一个断点,再单击一次红点就可以,以上就是通过IDE进行调试的方法。 3.5UI调试及视觉帮助工具 视频讲解 在main.dart文件的main()方法中可以添加更多的变量,例如debugpaintbaselinesenabled=true,添加之前,需要引入调试的包文件,代码如下: // Chapter03/03-05/lib/main.dart import 'package:flutter/rendering.dart'; //引入调试UI用的包文件 保存并重启应用,可以在模拟器中看到一些黄色和绿色的线,表示文字的基线和文字的位置,如图3.9所示。 图3.9UI调试 main()方法中还可以添加debugPaintPointersEnabled=ture,保存并重启应用后,我们发现没任何变化,但如果单击模拟器屏幕会突出显示,提示我们哪里有单击事件,这样就可以知道在哪里加监听。也可以在MaterialApp中设置调试变量,例如debugShowMaterialGrid: true。保存并重启应用,模拟器屏幕上显示了很多小的网格,这些网格对UI设计有帮助。以上的调试配置只在开发的过程中使用,它们可以帮助我们精确地计算每个元素之间的位置,因此如果想要找出两个元素的位置是否相同或居中,可以使用这种网格定位的方式检查。 错误经常发生,这一章我们学习了很多好用的工具来定位并改正错误,不同的错误有不同的调试方法。语法和编码错误IDE会有提示信息,可以把鼠标悬停在红线上查看错误信息。运行时错误通常会显示到屏幕或者控制台上,需要认真阅读这些错误信息。我们还学习了断点和调试器,断点可以帮助我们一步步地分析,同时可以看到返回的变量值。我们通过悬停,debug面板,watch来进行调试,也可以通过打印的方式进行调试。最后我们学习了调试页面上小部件的位置,来解决显示的问题。