使用断点进行调试

1. 准备工作

现在,大多数新手开发者可能都知道使用日志语句进行调试。学完第 1 单元后,您还将了解如何阅读堆栈轨迹和研究错误消息。虽然二者都是强大的调试工具,但现代 IDE 能够提供更多功能,使调试过程更加高效。

在这节课中,您将了解 Android Studio 的集成式调试程序、如何暂停执行应用,以及如何一次执行一行代码,以找到 bug 的确切来源。此外,您还将了解如何使用名为“Watches”的功能,并跟踪特定变量,而无需添加特定的日志语句。

前提条件

  • 您知道如何在 Android Studio 中浏览项目。
  • 熟悉 Kotlin 日志记录功能。

学习内容

  • 如何将调试程序连接到正在运行的应用。
  • 使用断点暂停正在运行的应用,并且一次检查一行代码。
  • 向断点添加条件表达式,以节省调试时间。
  • 向“Watches”窗格添加变量,以帮助进行调试。

所需条件

  • 一台安装了 Android Studio 的计算机。

2. 创建一个新项目

我们不是调试一个复杂的大型应用,而是从一个空白项目着手,并引入一些有 bug 的代码,以演示 Android Studio 中的调试工具。

首先,创建一个新的 Android Studio 项目。

  1. Select a Project Template 屏幕上,选择 Blank Activity

a949156bcfbf8a56.png

  1. 将应用命名为 Debugging,确保将语言设置为 Kotlin,其余所有内容都保持不变。

9863157e10628a87.png

  1. 您将看到一个新的 Android Studio 项目,其中显示了一个名为 MainActivity.kt 的文件。

e3ab4a557c50b9b0.png

引入 bug

还记得第 1 单元的调试课中的除数为零示例吗?在循环的最后迭代中,当应用尝试除以零时,应用会崩溃并抛出 java.langArithmeticException,因为除数不能为零。我们通过检查堆栈轨迹找到并修复了该 bug,并使用日志语句验证了这一假设。

因为您已熟悉该示例,所以我们将使用它来演示如何使用断点。断点可逐步一次运行一行代码,无需先添加日志语句并重新运行应用。

  1. 打开 MainActivity.kt 并将代码替换为以下代码:
package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

public val TAG = "MainActivity"

class MainActivity : AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       division()
   }

   fun division() {
       val numerator = 60
       var denominator = 4
       repeat(5) {
           Log.v(TAG, "${numerator / denominator}")
           denominator--
       }
   }

}
  1. 执行应用。注意应用是否按预期崩溃。

9468226e5f4d5729.png

3. 使用断点进行调试

在学习日志记录时,您学习了有策略地放置日志,以帮助发现 bug 并验证 bug 是否已修复。不过,如果遇到并非由您引入的 bug,有时候就无法确定应该在何处放入日志语句或输出哪些变量。通常,您只能在运行时找到这些信息。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}

这时候断点就派上用场了!即使您根据堆栈轨迹中的信息大致知道是什么导致了 bug,也可以添加断点,以此作为特定代码行的停止符号。到达某个断点后,代码会暂停执行,使您能够在运行时使用其他调试工具,以便仔细查看发生的情况和问题所在。

连接调试程序

在后台,Android Studio 会使用一个名为 Android 调试桥(简称 ADB)的工具。这是一个集成到 Android Studio 中的命令行工具,可以为正在运行的应用提供调试功能,如断点。用于调试的工具通常称为“调试程序”。

为了将调试程序用于(连接到)应用,您不能像以前那样简单地通过依次点击 Run > Run 来运行应用,而应通过依次点击 Run > Debug ‘app' 来运行应用。

340a8e850b3c86d3.png

向项目中添加断点

执行以下步骤来看看断点的实际运用:

  1. 点击您希望在其位置暂停的行号旁边的空白处来添加一个断点。该行号旁边会出现一个点,并且该行会突出显示。

629ac33dfb3873e.png

  1. 依次点击 Run > Debug ‘app' 或使用工具栏中的 a71c8b295db5927d.png 图标,在连接了调试程序的情况下运行应用。应用启动时,您应该会看到如下所示的屏幕:

3bd9cbe69d5a0d0e.png

应用启动后,您会看到断点被激活时突出显示。

928fc1194966c9.png

在您之前查看 Logcat 窗口的屏幕底部,系统打开了一个新的 Debug 标签页。

447d9743c118babd.png

左侧是函数列表,它们与堆栈轨迹中显示的列表相同。右侧是一个窗格,您可以在其中查看当前函数(即 division())中各个变量的值。顶部还提供了按钮,可以帮助您在已暂停的程序中导航。您将最常用的一个按钮是 Step Over,点按后会执行突出显示的一行代码。

48219b96d5ab6ba6.png

执行以下步骤来调试代码:

  1. 到达断点后,第 19 行(声明 numerator 变量)现已突出显示,但尚未运行。使用 Step Over fbdcba647f9844c4.png 按钮执行第 19 行。现在,第 20 行将突出显示。

eaacf76805166461.png

  1. 在第 22 行设置一个断点。这是出现除法的位置,也是堆栈轨迹报告异常的那一行。

1f18ab31dc58a1a7.png

  1. 使用 Debug 窗口左侧的 Resume Program 616d16841834ae2a.png 按钮转到下一个断点,并运行 division() 函数的其余部分。

3a9c3edc893f9720.png

  1. 请注意,在第 17 行停止了执行,未执行该行。

aa56331ad870cd40.png

  1. 每个变量(numeratordenominator)的值都显示在其声明旁边。您可以在“Debug”窗口的 Variables 标签页中看到这些变量的值。

5b3515c5580ee7dd.png

  1. 再按“Debug”窗口左侧的 Resume Program 按钮 4 次。每次循环暂停并观察 numeratordenominator 的值。在最后一次迭代时,numerator 应该为 60denominator 应该为 0。但不能将 60 除以 0!

56ea223612b88125.png

现在,您已经知道了导致 bug 的确切代码行,并且知道了确切的原因。和之前一样,您可以通过将重复代码的次数从 5 更改为 4 来修复 bug。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(4) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}

4. 设置断点的条件

在上一部分中,您必须遍历每个循环迭代,直到分母为零。在更复杂的应用中,如果您掌握的 bug 信息较少,这可能会很繁琐。不过,如果您有一个假设(例如,应用只有在分母为零时才会崩溃),则可以修改断点,以便仅在满足此假设时才到达断点,而不必遍历每个循环迭代。

  1. 如有必要,通过在重复循环中将 4 更改为 5,重新引入 bug。
repeat(4) {
    ...
}
  1. 在包含 repeat 语句的行上添加新的断点。

47fcc3aeb814a9d7.png

  1. 右键点击红色断点图标。这时会出现一个菜单,其中包含一些选项,例如是否启用断点。已停用的断点仍然存在,但不会在运行时被触发。您还可以选择添加 Kotlin 表达式:如果计算结果为 true,则会触发断点。例如,如果您使用 denominator > 3 表达式,则该断点将仅在循环第一次迭代时触发。如需仅在应用有可能要除以零时触发该断点,请将表达式设置为 denominator == 0。断点的选项应如下所示:

76045ef783d5389b.png

  1. 依次点击 Run > Debug 'app' 运行应用,并观察是否已到达断点。

74ba264a6eab7db7.png

您可以看到分母已经为 0。系统只有在满足条件时才会触发断点,这样就无需遍历代码,节省了时间和精力。

153be06e8a19e61d.png

  1. 和之前一样,您会看到 bug 是由循环执行一行代码次数过多导致的,此时分母设置为 0。

添加监测条件

如果您想在调试时监控特定值,则无需在 Variables 标签页中搜索查找该值。您可以添加“Watches”来监控特定变量。这些变量将显示在调试窗格中。当执行暂停且变量在作用域内时,它将在“Watches”窗格中显示。这样可以提高处理大型项目时的调试效率。您将能够在一个位置跟踪所有相关变量。

  1. 在调试视图中,在“Variables”窗格的右侧应该还有一个名为 Watches 的空白窗格。点击左上角的加号 c97b1f6a879b0563.png 按钮。您会看到一个菜单选项,显示 New Watch

4d27cc7c5222377b.png

  1. 在提供的字段中输入变量名称 denominator,然后点击 Enter。
  2. 依次点击 Run > debug'app' 重新运行应用,您会发现:在遇到断点时,您会在“Watches”窗格中看到分母的值。

5. 恭喜

总结:

  • 您可以设置断点来暂停应用的执行。
  • 执行暂停后,您可以“单步”只执行一行代码。
  • 您可以将条件语句设置为仅根据 Kotlin 表达式触发断点。
  • 通过“Watches”功能,您可以在调试时对感兴趣的变量进行分组。

了解详情