调试您的应用

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

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

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

启用调试

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

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

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

通常,您可以选择每个 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. 如果您使用的是模拟器或已root的设备,则可以选中显示所有进程以查看所有进程。在已root的设备上,这将显示设备上运行的所有进程。但是,在未root的设备上,这只会显示可调试的进程。
    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 调试类型。
仅 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 数据类型渲染器中的说明操作。