请参考教材,全面理解和完成本章节内容... ...
复制第三章的工程,将工程目录改名为ch4。
为练习应用调试,我们先刻意搞点破坏。打开QuizActivity.java文件,在onCreate(Bundle)方法中,注释掉“获取TextView组件并赋值给mQuestionTextView变量”的那行代码,如代码清单4-1所示。
代码清单4-1 注释掉一行关键代码(QuizActivity.java)
运行GeoQuiz应用,看看会发生什么,如图4-1所示。
图4-1 GeoQuiz应用崩溃了
图4-1展示了应用崩溃后的消息提示画面。不同Android版本的消息提示可能略有不同,但本质上它们都是同一个意思。当然,这里我们知道应用为何崩溃。但假如不知道应用为何出现异常,下面将要介绍的Android->LogCat有助于问题的排查。
4.1 查看异常
之前, 介绍过如何利用LogCat查看应用的运行日志。现在, 我们将利用LogCat查看导致应用崩溃的异常。
可在 Android Studio 最下方的找到 6:Android,点击它,打开LogCat。如图4-2所示。
图4-2 打开LogCat
4.2 分析异常与栈跟踪
现在回头来看应用崩溃的问题。为方便查看异常或错误信息,可展开LogCat窗口。上下滑动滚动条,最终应该会看到一大片暗红色的异常或错误信息(不同版本和设备内容有差别)。这就是标准的Android运行时的异常信息报告,如图4-3所示。
图4-3 LogCat中的异常与栈追踪
该异常报告首先告诉了我们最高层级的异常及其栈追踪,然后是导致该异常的异常及其栈追踪。如此不断追溯,直到找到一个没有原因的异常“NullPointerException” 如图4-3箭头处。
在大部分编写的代码中,最后一个没有原因的异常往往是我们要关注的目标。这里,没有原因的异常是java.lang.NullPointerException
。紧接着该异常语句下面的一行就是其栈追踪信息的第一行。从该行可以看出发生异常的类和方法以及它所在的源文件及代码行号(蓝色标记)。双击该行,会自动跳转到源代码的对应行上。
4.2.1 诊断应用异常
(使用Android Studio 调试程序,太幸福了!)
应用出错不一定总会导致应用崩溃。某些时候,应用只是出现了运行异常。例如,每次单击「下一个」按钮时,应用都毫无反应。这就是一个非崩溃型的应用运行异常。
在QuizActivity.java中,修改mNextButton
监听器代码,将mCurrentIndex
变量递增的语句注释掉,如代码清单4-2所示。
代码清单4-2 注释掉一行关键代码(QuizActivity.java)
运行GeoQuiz应用,点击「下一个」按钮。可以看到,应用毫无响应,如图4-4所示。
图4-4 应用不响应Next按钮的点击
这个问题要比上个更为棘手。它没有抛出异常,所以修正这个问题不像前面跟踪追溯并消除异常那么简单。有了解决上个问题的经验,这里可以推测出导致该问题的两种可能因素:
mCurrentIndex
变量值没有改变;updateQuestion()
方法没有调用成功。
如不知道问题产生的原因,则需要设法跟踪并找出问题所在。在接下来的几小节里,我们将学习到两种跟踪问题的方法:
- 记录栈跟踪的诊断性日志;
- 利用调试器设置断点调试。
4.2.2 记录栈跟踪日志
在QuizActivity
中,为updateQuestion()
方法添加日志输出语句,如代码清单4-3所示。
代码清单4-3 方便实用的调试方式(QuizActivity.java)
如同前面AndroidRuntime
的异常,Log.d(String, String, Throwable)
方法记录并输出整个栈跟踪信息。借助栈跟踪日志,可以很容易看出updateQuestion()
方法在哪些地方被调用了。
作为参数传入Log.d()
方法的异常不一定是我们捕获的已抛出异常。创建一个新的Exception()
方法,把它作为不抛出的异常对象传入该方法也是可以的。借此,我们得到异常发生位置的记录报告。
运行GeoQuiz应用,点击Next按钮,然后在LogCat中查看日志输出,日志输出结果如图4-5所示。
图4-5 日志输出结果
栈跟踪日志的第一行即调用异常记录方法的地方。紧接着的两行表明,updateQuestion()
方法是在onClick()
实现方法里被调用的。双击该行即可跳转至注释掉的问题索引递增代码行。暂时保留该代码问题,下一节我们会使用设置断点调试的方法重新查找该问题。
记录栈跟踪日志虽然是个强大的工具,但也存在缺陷。比如,大量的日志输出很容易导致LogCat窗口信息混乱难读。此外,通过阅读详细直白的栈跟踪日志并分析代码意图,竞争对手可以轻易剽窃我们的创意。
另一方面,既然有时可以从栈跟踪日志看出代码的实际使用意图,在网站上寻求帮助时,附上一段栈跟踪日志往往有助于更快地解决问题。根据需要,我们既可以直接从LogCat中复制并粘贴日志内容,也可以将日志保存到文本文件中。
继续学习前,先注释掉QuizActivity.java中的MYTAG常量,然后删除日志记录代码,如清单4-4所示。
代码清单4-4 又见老朋友(Log.d()
方法)(QuizActivity.java)
注释掉MYTAG常量会移除未使用的变量警告。当然,也可以删除MYTAG常量,但最好不要这样做。因为,保不准什么时候还会用它来记录日志消息。
4.2.3 设置断点
要使用 Android Studio 自带的代码调试器跟踪调试上一节中我们遇到的问题,首先需要在updateQuestion()
方法中设置断点,以确认该方法是否被调用。断点会在断点设置行的前一行代码处停止运行,然后我们可以逐行检查代码,看看接下来到底发生了什么。
打开QuizActivity.java文件,找到updateQuestion()
方法,双击方法中第一行代码左边的灰色栏区域。可以看到,灰色栏上出现了一个红色圈圈。这就是我们设置的一处断点,如图4-6所示。
图4-6 已设置的一处断点
想要启用代码调试器并触发已设置的断点,我们需要调试运行而不是直接运行应用。要调试运行应用,选择Run->Debug 'GeoQuiz' 菜单项,或点击红色箭头指向的小虫子,开始进入调试状态,如图4-7所示。
图4-7 进入代码调试状态
以调试方式应用启动后,应用首先调用QuizActivity.onCreate(Bundle)
方法,接着调用updateQuestion()
方法,然后触发updateQuestion()
方法内的断点,应用将会暂停。
应用在断点处停止了运行,断点设置所在行的代码也被加亮显示了, 如图4-8所示。
图4-8 代码调试视图
另外,触发断点后,代码调试视图5:Debug 会自动打开, 如图4-9所示。
图4-9 变量查看视图
左侧视图显示了当前的代码调用栈。从栈中可以看到,程序先调用onCreate方法,接着开始执行updateQuestion方法,然后在断点处暂停。
图4-9中,右侧是变量视图,断点处各对象的值都可以在该视图中观察到,单击this旁边的三角展开按钮或点击右箭头键可看到全部变量值。
图4-9顶部有一排按钮,它们是Android Studio提供的多种代码调试方法, 常用方法如下:
- Step over 是单步执行(调试)程序,遇到子函数时不会进入子函数内单步执行,它把子函数整个作为一步。快速调试程序最常用。
- step into 也是单步执行程序,但遇到子函数就进入并在子函数内部继续单步执行,执行完毕返回上一层函数。在不存在子函数的情况下是和step over效果一样的。
- step out,就是但单步执行到子函数内部时,用step out就可以执行完子函数余下部分,并返回到上一层函数。
- Resume Program 不是单步执行(调试)程序,点击它程序会一直运行到下一个断点(或者循环回到当前断点)。
从栈列表可以看出updateQuestion()
方法已经在onCreate(Bundle)
方法中被调用了。不过,我们需要关心的是检查Next按钮被点击后的行为。因此单击Resume按钮让程序继续运行。然后,再次点击“下一步”按钮观察断点是否被激活(应该被激活)。
图4-9中,右侧是变量视图显示各对象的值,我们现在只需关心变量mCurrentIndex
的值。在变量视图里找到mCurrentIndex
,它现在的值为0。
代码看上去没问题。为继续追查,需跳出当前updateQuestion
方法。单击Step Out按钮跳到mNextButton
的 OnClickListener
方法,正好是在updateQuestion()
方法被调用之后。真是相当方便的调试,问题解决了。(点击“下一步”按钮后mCurrentIndex
没有被更新)
接下来我们来修复代码问题。不过,在修改代码前,必须先停止调试应用。选择Run->Stop菜单项停止程序。然后在OnClickListener
方法中取消对mCurrentIndex
语句的注释,如代码清单4-5所示。
代码清单4-5 取消代码注释(QuizActivity.java)
代码修复后,再点击一下断点,清除断点设置。