调试您的应用

Android Studio 提供了一个调试器,可让您执行以下操作,以及更多操作。

  • 选择设备以调试您的应用。
  • 在您的 Java、Kotlin 和 C/C++ 代码中设置断点。
  • 在运行时检查变量并评估表达式。

本页面包含基本调试器操作的说明。有关更多文档,另请参阅 IntelliJ IDEA 调试文档

启用调试

在开始调试之前,请执行以下操作

在您的设备上启用调试。
如果您使用的是模拟器,则默认情况下会启用调试。但对于连接的设备,您需要 在设备开发者选项中启用调试
运行可调试的构建变体。

使用包含 构建变体 debuggable true (isDebuggable = true 在 Kotlin 脚本中) 的构建配置。

通常,您可以选择默认的“debug”变体,该变体包含在每个 Android Studio 项目中,即使它在 build.gradle 文件中不可见。但是,如果您定义了应该可调试的新构建类型,则必须将 debuggable true 添加到构建类型

Groovy

android {
    buildTypes {
        customDebugType {
            debuggable true
            ...
        }
    }
}

Kotlin

android {
    buildTypes {
        create("customDebugType") {
            isDebuggable = true
            ...
        }
    }
}

此属性也适用于 包含 C/C++ 代码的模块

注意:jniDebuggable 属性不再使用。

如果您的应用依赖于您也想要调试的库模块,则该库也必须使用 debuggable true 打包,以便它保留其调试符号。为了确保您的应用项目的可调试变体接收库模块的可调试变体,请发布库的非默认版本。

开始调试

您可以按如下方式启动调试会话

  1. 在您的应用代码中设置 断点
  2. 在工具栏中,从目标设备菜单中选择一个设备来调试您的应用。
    Target device menu.
    图 1.目标设备菜单。

    如果您没有配置任何设备,则需要通过 USB 连接设备,通过 Wi-Fi 连接设备,或者 创建 AVD 以使用 Android 模拟器

  3. 在工具栏中,点击调试

    如果您的应用已在设备上运行,则会显示一个对话框,询问您是否要从运行切换到调试。设备需要重新启动才能开始调试。要使应用程序的相同实例继续运行,请点击取消调试,并改为 将调试器附加到正在运行的应用程序。否则,Android Studio 将构建一个 APK,使用调试密钥对其进行签名,将其安装到您选择的设备上,并运行它。

    如果您 向项目添加 C 和 C++ 代码,Android Studio 还会在调试窗口中运行 LLDB 调试器 以调试您的本机代码。

  4. 如果调试窗口没有打开,请选择查看 > 工具窗口 > 调试,或在工具窗口栏中点击调试

将调试器附加到正在运行的应用程序

如果您的应用程序已在您的设备上运行,您可以按如下方式开始调试,而无需重新启动您的应用程序

  1. 点击将调试器附加到 Android 进程
  2. 选择进程对话框中,选择要将调试器附加到的进程。
    1. 如果您使用的是模拟器或已植根的设备,则可以选中显示所有进程以查看所有进程。在已植根的设备上,这将显示设备上运行的所有进程。但是,在未植根的设备上,这将仅显示可调试的进程。
    2. 使用 Android 调试器设置来自菜单中,您可以选择现有的 运行/调试配置。对于 C 和 C++ 代码,这允许您在现有配置中重复使用 LLDB 启动命令、LLDB 后附加命令和符号目录。
    3. 如果您没有现有的运行/调试配置,请选择创建新的。此选择将启用调试类型菜单,您可以在其中 选择不同的调试类型。默认情况下,Android Studio 使用自动检测调试类型来为您选择最佳的调试器选项,具体取决于您的项目是否包含 Java 或 C/C++ 代码。
  3. 点击确定

    调试窗口将出现。

设备资源管理器中的进程选项卡(查看 > 工具窗口 > 设备资源管理器)也列出了可调试的进程。从那里,您可以选择一个进程并执行终止 、强制停止 ,或将调试器附加到给定进程

调试窗口

图 2.调试窗口。

调试窗口分为

  1. 执行和导航工具栏 查看 使用断点
  2. 线程选择器
  3. 评估和监视表达式输入。查看 检查变量
  4. 堆栈显示
  5. 变量窗格。查看 检查变量

注意:Android Studio 调试器和垃圾收集器松散集成。Android 虚拟机保证调试器已知的任何对象都不会在调试器断开连接之前被垃圾收集。这会导致在调试器连接时对象堆积。例如,如果调试器看到一个正在运行的线程,则关联的 Thread 对象不会在调试器断开连接之前被垃圾收集,即使线程已终止。

更改调试器类型

由于调试 Java/Kotlin 代码和 C/C++ 代码需要不同的调试器工具,因此 Android Studio 调试器允许您选择要使用的调试器类型。默认情况下,Android Studio 会根据它在项目中检测到的语言自动决定要使用的调试器,使用自动检测调试器类型。

要手动选择 调试配置 中的调试器,请点击运行 > 编辑配置。您也可以在点击运行 > 将调试器附加到 Android 进程时出现的对话框中选择调试器。

可用的调试类型包括以下内容

自动检测
如果您希望 Android Studio 自动选择要调试的代码的最佳选项,请选择此调试类型。例如,如果您的项目中包含任何 C 或 C++ 代码,Android Studio 将自动使用双重调试类型。否则,Android Studio 将使用 Java-Only 调试类型。
仅 Java
如果您只想调试用 Java 或 Kotlin 编写的代码,请选择此调试类型。仅 Java 调试器会忽略您在本地代码中设置的任何断点或监视。
仅本机(仅适用于 C/C++ 代码)
如果您只想使用 LLDB 调试代码,请选择此调试类型。使用此调试类型时,Java 调试器会话视图不可用。默认情况下,LLDB 仅检查您的本机代码,并忽略 Java 代码中的断点。如果您还想调试 Java 代码,请切换到自动检测或双重调试类型。

本机调试仅适用于满足以下要求的设备

  • 设备支持 run-as

    要检查设备是否支持 run-as,请在连接到设备的 ADB shell 上运行以下命令

    run-as your-package-name pwd
    

    your-package-name 替换为您的应用的包名。如果设备支持 run-as,则命令应返回,没有任何错误。

  • 设备已启用 ptrace

    要检查 ptrace 是否已启用,请在连接到设备的 ADB shell 上运行以下命令

    sysctl kernel.yama.ptrace_scope
    

    如果 ptrace 已启用,则命令将打印值 0unknown key 错误。如果 ptrace 未启用,则它将打印一个非 0 的值。

双重(Java + 本机) - 仅适用于 C/C++ 代码
如果您希望在调试 Java 和本机代码之间切换,请选择此调试类型。Android Studio 会将 Java 调试器和 LLDB 都附加到您的应用程序进程,以便您可以在 Java 和本机代码中检查断点,而无需重新启动应用程序或更改调试配置。

在图 2 中,注意调试窗口标题右侧的两个选项卡。由于应用程序同时包含 Java 和 C++ 代码,因此一个选项卡用于调试本机代码,另一个选项卡用于调试 Java 代码,如-java所示。

图 3.用于调试本机代码的选项卡和用于调试 Java 代码的选项卡。

注意:调试由编译器优化的本机代码时,您可能会收到以下警告消息
此函数使用启用的优化进行编译。某些调试器功能可能不可用。使用优化标志时,编译器会对已编译的代码进行更改以使其运行得更高效。这会导致调试器报告意外或不正确的信息,因为调试器很难将已优化的已编译代码映射回原始源代码。因此,您应该在调试本机代码时禁用编译器优化。

使用系统日志

系统日志在您调试应用程序时显示系统消息。这些消息包括来自设备上运行的应用程序的信息。如果您想使用系统日志调试应用程序,请确保您的代码写入日志消息并在应用程序处于开发阶段时打印异常的堆栈跟踪。

在代码中写入日志消息

要在代码中写入日志消息,请使用 Log 类。日志消息通过收集系统调试输出,帮助您了解执行流程,同时与您的应用程序交互。日志消息还可以告诉您应用程序的哪个部分失败了。有关日志记录的更多信息,请参阅 使用 Logcat 写入和查看日志

以下示例展示了您可能如何在活动启动时添加日志消息以确定之前状态信息是否可用

Kotlin

import android.util.Log
...
class MyActivity : Activity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        if (savedInstanceState != null) {
            Log.d(TAG, "onCreate() Restoring previous state")
            /* restore state */
        } else {
            Log.d(TAG, "onCreate() No saved state available")
            /* initialize app */
        }
        ...
    }
  ...
  companion object {
    private val TAG: String = MyActivity::class.java.simpleName
    ...
  }
}

Java

import android.util.Log;
...
public class MyActivity extends Activity {
    private static final String TAG = MyActivity.class.getSimpleName();
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
       ...
       if (savedInstanceState != null) {
            Log.d(TAG, "onCreate() Restoring previous state");
            /* restore state */
        } else {
            Log.d(TAG, "onCreate() No saved state available");
            /* initialize app */
        }
        ...
    }
}

在开发过程中,您的代码还可以捕获异常并将堆栈跟踪写入系统日志

Kotlin

fun someOtherMethod() {
    try {
        ...
    } catch (e : SomeException) {
        Log.d(TAG, "someOtherMethod()", e)
    }
}

Java

void someOtherMethod() {
    try {
        ...
    } catch (SomeException e) {
        Log.d(TAG, "someOtherMethod()", e);
    }
}

注意:准备好发布应用程序后,请从代码中删除调试日志消息和堆栈跟踪打印调用。为此,请设置一个 DEBUG 标志,并将调试日志消息放置在条件语句中。

查看系统日志

您可以在 Logcat 窗口中查看和过滤调试和其他系统消息,如图 4 所示。例如,您可以在垃圾收集发生时或您使用 Log 类添加到应用程序中的消息时看到消息。

要使用 Logcat,请 开始调试 并选择 Logcat 选项卡。

图 4.带过滤器设置的 Logcat 窗口。

有关 Logcat 及其过滤选项的说明,请参阅 使用 Logcat 写入和查看日志

使用断点

Android Studio 支持触发不同调试操作的断点。断点有几种类型

行断点
最常见的类型是行断点,它会暂停应用程序在指定代码行的执行。暂停时,您可以检查变量、评估表达式,然后逐行继续执行以确定运行时错误的原因。
方法断点
方法断点会在您的应用程序进入或退出特定方法时暂停执行。在暂停期间,您可以检查变量、评估表达式,然后逐行继续执行,以确定运行时错误的原因。当您在可组合函数上设置断点时,调试器会列出可组合函数的参数及其状态,以帮助确定哪些更改可能导致了重新组合。
字段断点
字段断点会在您的应用程序读取或写入特定字段时暂停执行。
异常断点
异常断点会在抛出异常时暂停您的应用程序的执行。

您可以设置条件断点,仅在满足特定条件时才会暂停执行。您还可以设置日志记录断点,这些断点会在不暂停执行的情况下写入 Logcat。这有助于避免在代码中添加过多的日志语句。

要添加行断点,请执行以下操作

  1. 找到您要暂停执行的代码行。
  2. 单击该代码行左侧的边距,或将光标放在该行上并按下 Control+F8(在 macOS 上,按下 Command+F8)。
  3. 如果您的应用程序已在运行,请单击 **将调试器附加到 Android 进程** 。否则,要开始调试,请单击 **调试**

设置断点后,该行旁边会出现一个红点,如图 5 所示。

图 5. 设置断点后,该行旁边会出现一个红点。

当代码执行到达断点时,Android Studio 会暂停您的应用程序的执行。

要确定应用程序的状态,请使用调试器选项卡中的工具

  • 要检查变量的对象树,请在变量视图中展开它。如果变量视图不可见,请单击 **布局设置** 并确保已选中 **变量**。

  • 要进入代码的下一行而不进入方法,请单击 **步过**

  • 要进入方法调用的第一行,请单击 **步入**

  • 要进入当前方法之外的下一行,请单击 **步出**

  • 要继续正常运行应用程序,请单击 **恢复程序**

如果您的项目使用任何原生代码,默认情况下,自动检测调试类型会将 Java 调试器和 LLDB 作为两个独立的进程附加到您的应用程序。您可以在不重新启动应用程序或更改设置的情况下,在检查 Java 和 C/C++ 断点之间切换。

注意:要使 Android Studio 检测 C 或 C++ 代码中的断点,您需要使用支持 LLDB 的调试类型,例如自动检测、原生或双重。您可以通过 编辑调试配置 来更改 Android Studio 使用的调试类型。要详细了解不同的调试类型,请阅读有关使用其他 调试类型 的部分。

当 Android Studio 将您的应用程序部署到目标设备时,调试窗口会打开,其中包含每个调试器进程的选项卡或调试会话视图,如图 6 所示。

图 6. 使用 LLDB 调试原生代码。
  1. 当 LLDB 调试器遇到 C/C++ 代码中的断点时,Android Studio 会切换到 <your-module> 选项卡。框架、变量和监视窗格也可用,它们的工作方式与调试 Java 代码时完全相同。

    虽然线程窗格在 LLDB 会话视图中不可用,但您可以使用框架窗格中的列表访问应用程序进程。在有关如何 调试窗口框架检查变量 的部分中详细了解这些窗格。

    注意:在检查原生代码中的断点时,Android 系统会暂停运行应用程序 Java 字节码的虚拟机。这意味着在检查原生代码中的断点时,您无法与 Java 调试器交互或从 Java 调试器会话中检索任何状态信息。

  2. 当 Java 调试器遇到 Java 或 Kotlin 代码中的断点时,Android Studio 会切换到 <your-module>-java 选项卡。
  3. 在使用 LLDB 调试时,您可以使用 LLDB 会话视图中的 LLDB 终端将 命令行选项传递给 LLDB。如果您希望 LLDB 在每次开始调试应用程序时执行某些命令,无论是调试器附加到应用程序进程之前还是之后,您都可以 将这些命令添加到调试配置 中。

在调试 C/C++ 代码时,您还可以设置特殊类型的断点,称为监视点,当应用程序与特定内存块交互时,这些断点可以暂停您的应用程序进程。要详细了解,请阅读有关如何 添加监视点 的部分。

查看和配置断点

要查看所有断点并配置断点设置,请在调试窗口中单击 **查看断点** 。断点窗口将出现,如图 7 所示。

图 7. 断点窗口列出所有当前断点,并包含每个断点的行为设置。

断点窗口允许您在窗格中的列表中启用或禁用每个断点。如果断点被禁用,Android Studio 不会在遇到该断点时暂停您的应用程序。

从列表中选择一个断点以配置其设置。您可以将断点配置为最初处于禁用状态,并在遇到另一个断点后由系统启用。您还可以配置断点在被命中后是否应该被禁用。要为任何异常设置断点,请在断点列表中选择 **异常断点**。

要暂时禁用所有断点,请在调试窗口中单击 **静音断点** 。再次单击以重新启用。

调试窗口框架

在调试器窗口中,框架窗格允许您检查导致当前断点触发的堆栈帧。这使您可以导航和检查堆栈帧,还可以检查 Android 应用程序中的线程列表。

要选择一个线程,请使用线程选择器菜单并查看其堆栈帧。单击框架中的元素以在编辑器中打开源代码。您还可以自定义线程演示并导出堆栈帧,如 检查框架指南 中所述。

检查变量

在调试器窗口中,变量窗格允许您在系统在断点处停止应用程序并从框架窗格中选择一个框架时检查变量。变量窗格还允许您使用在选定框架内可用的静态方法和/或变量评估临时表达式。

要将表达式添加到对象树(在应用程序正在调试时)

图 8. 调试窗口中的对象树和表达式输入框。
  1. 输入要监视或显示的表达式
  2. 单击 **添加到监视** 或按 Enter 键评估表达式一次。

或者,如果对象树包含您要监视的表达式,您可以将其拖动到树的顶部将其添加为监视表达式。

监视表达式将在遇到断点或您逐步执行代码时更新。

已评估的表达式将保留在对象树的顶部,直到您手动评估另一个表达式或逐步执行代码。

要从对象树中删除监视表达式,请右键单击表达式,然后单击 **删除监视**。

添加监视点

在调试 C/C++ 代码时,您可以设置特殊类型的断点,称为监视点,当应用程序与特定内存块交互时,这些断点可以暂停您的应用程序进程。例如,如果您将两个指针指向内存块,并为其分配监视点,则使用任何一个指针访问该内存块都会触发监视点。

在 Android Studio 中,您可以通过选择特定变量在运行时创建监视点,但 LLDB 仅将监视点分配给系统分配给该变量的内存块,而不是分配给变量本身。这与将变量添加到监视窗格不同,监视窗格使您可以观察变量的值,但不会让您在系统在内存中读取或更改其值时暂停应用程序进程。

注意:当应用程序进程退出函数,系统从内存中释放其局部变量时,您需要重新分配为这些变量创建的任何监视点。

要设置监视点,您必须满足以下要求

  • 您的目标物理设备或模拟器使用 x86 或 x86_64 CPU。如果您的设备使用 ARM CPU,那么您必须将变量地址在内存中的边界对齐到 4 个字节(对于 32 位处理器)或 8 个字节(对于 64 位处理器)。要对齐原生代码中的变量,请在变量声明中指定 __attribute__((aligned(num_bytes))),如下所示
    // For a 64-bit ARM processor
    int my_counter __attribute__((aligned(8)));
    
  • 您已经分配了三个或更少的监视点。Android Studio 在 x86 或 x86_64 目标设备上仅支持最多四个监视点。其他设备可能支持更少的监视点。

注意:使用 32 位 ARM ABI 调试应用程序时,添加监视点或将鼠标悬停在代码中的变量上以调查其值可能会导致崩溃。作为解决方法,请使用 64 位 ARM、x86 或 x86_64 二进制文件进行调试。此问题将在即将发布的 Android Studio 版本中修复。

如果您满足要求,则可以按如下方式添加监视点

  1. 当应用程序在断点处暂停时,请导航到 LLDB 会话视图中的变量窗格。
  2. 右键单击占用您要跟踪的内存块的变量,然后选择 **添加监视点**。

    图 9. 向内存中的变量添加监视点。
  3. 将出现一个配置监视点的对话框,如图 9 所示。

    使用以下选项配置监视点

    • 启用:如果您希望告诉 Android Studio 忽略监视点,直到您更改设置,请取消选中此选项。Android Studio 会保存您的监视点,以便您可以稍后访问它。
    • 暂停:默认情况下,Android 系统会在访问分配给监视点的内存块时暂停应用程序进程。如果您不希望出现这种行为,请取消选中此选项。这将显示您可以用来自定义系统与监视点交互时的行为的其他选项:将消息记录到控制台命中时删除
    • 访问类型:选择应用程序在尝试读取写入系统分配给变量的内存块时是否应触发监视点。要触发读或写操作的监视点,请选择任何
  4. 单击 **完成**。

要查看所有监视点并配置监视点设置,请在调试窗口中单击 **查看断点** 。将出现断点对话框,如图 10 所示。

图 10. 断点对话框列出您当前的监视点,并包含每个监视点的行为设置。

添加监视点后,请在调试窗口中单击 **恢复程序** 以恢复应用程序进程。默认情况下,如果应用程序尝试访问您已为其设置监视点的内存块,Android 系统会暂停应用程序进程,并且监视点图标 会出现在应用程序最后执行的代码行旁边,如图 11 所示。

图 11. Android Studio 指示应用程序在触发监视点之前执行的代码行。

查看和更改资源值显示格式

在调试模式下,您可以查看资源值并为 Java 或 Kotlin 代码中的变量选择不同的显示格式。在显示“变量”选项卡并选择一个框架后,请执行以下操作:

  1. 在“变量”列表中,右键单击资源行上的任意位置以显示列表。
  2. 在列表中,选择“以…显示”并选择要使用的格式。

    可用的格式取决于您选择的资源的数据类型。您可能会看到以下一个或多个选项:

    • 类:显示类定义。
    • toString:显示字符串格式。
    • 对象:显示对象(类的实例)定义。
    • 数组:以数组格式显示。
    • 时间戳:显示日期和时间,如下所示:yyyy-mm-dd hh:mm:ss。
    • 自动:Android Studio 会根据数据类型选择最佳格式。
    • 二进制:使用零和一显示二进制值。
    • MeasureSpec:从父级传递给所选子级的值。参见 MeasureSpec.
    • 十六进制:以十六进制值显示。
    • 原始值:使用原始数据类型以数值形式显示。
    • 整数:Integer 类型的数值显示。

要创建自定义格式,请执行以下操作:

  1. 右键单击资源值。
  2. 选择“以…显示”。
  3. 选择“创建”。
  4. Java 数据类型渲染器 对话框将显示。请按照 Java 数据类型渲染器 中的说明操作。