调试应用

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

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

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

启用调试

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

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

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

通常,您可以选择每个 Android Studio 项目中都包含的默认“debug”变体,即使它在 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 Emulator

  3. 在工具栏中,点击Debug

    如果您的应用已在设备上运行,则会显示一个对话框,询问您是否要从运行切换到调试。设备需要重启才能开始调试。要保持同一应用实例运行,请点击Cancel Debug,然后将调试器附加到正在运行的应用。否则,Android Studio 会构建一个 APK,使用调试密钥对其进行签名,将其安装到您选择的设备上,然后运行它。

    如果您将 C 和 C++ 代码添加到项目中,Android Studio 还会运行调试窗口中的 LLDB 调试器来调试您的原生代码。

  4. 如果调试窗口未打开,请选择View > Tool Windows > Debug,或点击工具窗口栏中的Debug

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

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

  1. 点击Attach debugger to Android process
  2. Choose Process对话框中,选择要附加调试器的进程。
    1. 如果您使用的是模拟器或已获得 Root 权限的设备,您可以勾选 Show all processes 以查看所有进程。在已获得 Root 权限的设备上,这将显示设备上运行的所有进程。但是,在未获得 Root 权限的设备上,这只会显示可调试的进程。
    2. Use Android Debugger Settings from菜单中,您可以选择现有的运行/调试配置。对于 C 和 C++ 代码,这使您可以重用现有配置中的 LLDB 启动命令、LLDB 附加后命令和符号目录。
    3. 如果您没有现有的运行/调试配置,请选择Create New。此选择会启用Debug Type菜单,您可以在其中选择不同的调试类型。默认情况下,Android Studio 使用“自动检测”调试类型,根据您的项目是否包含 Java 或 C/C++ 代码来为您选择最佳的调试器选项。
  3. 点击OK

    调试窗口随即显示。

设备浏览器(View > Tool Windows > Device Explorer)中的Processes标签页也包含可调试进程列表。从中,您可以选择一个进程并执行终止、强制停止,或将调试器附加到给定进程

调试窗口

图 2.调试窗口。

调试窗口分为:

  1. 执行和导航工具栏。请参阅处理断点
  2. 线程选择器
  3. 评估和观察表达式输入。请参阅检查变量
  4. 堆栈显示
  5. 变量窗格。请参阅检查变量

注意:Android Studio 调试器和垃圾回收器之间的集成度不高。Android 虚拟机可确保调试器了解的任何对象在调试器断开连接之前不会被垃圾回收。这可能导致在调试器连接时对象堆积。例如,如果调试器看到正在运行的线程,则关联的 Thread 对象在调试器断开连接之前不会被垃圾回收,即使该线程已终止。

更改调试器类型

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

要在调试配置中手动选择调试器,请点击Run > Edit Configurations。您也可以在点击Run > Attach debugger to Android process时出现的对话框中选择调试器。

可用的调试类型包括:

自动检测
如果您希望 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. 如果您的应用已在运行,请点击Attach debugger to Android process 。否则,要开始调试,请点击Debug

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

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

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

要识别应用的状态,请使用调试器标签页中的工具:

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

  • 要在不进入方法的情况下前进到代码中的下一行,请点击Step Over

  • 要前进到方法调用内的第一行,请点击Step Into

  • 要前进到当前方法之外的下一行,请点击Step Out

  • 要正常继续运行应用,请点击Resume Program

如果您的项目使用任何原生代码,默认情况下,“自动检测”调试类型会将 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> 标签页。Frames、Variables 和 Watches 窗格也可用,并且与调试 Java 代码时的工作方式完全相同。

    尽管线程窗格在 LLDB 会话视图中不可用,但您可以使用 Frames 窗格中的列表访问您的应用进程。在有关如何调试窗口框架检查变量的部分中了解有关这些窗格的更多信息。

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

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

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

查看和配置断点

要查看所有断点并配置断点设置,请点击调试窗口中的View Breakpoints 。断点窗口随即显示,如图 7 所示。

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

通过“断点”窗口,您可以从窗格中的列表中启用或禁用每个断点。如果断点被禁用,则 Android Studio 在命中该断点时不会暂停您的应用。

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

要暂时禁用所有断点,请点击调试窗口中的Mute Breakpoints 。再次点击以重新启用。

调试窗口框架

在调试器窗口中,“帧”窗格可让您检查导致当前断点被命中的堆栈帧。这使您能够导航和检查堆栈帧,以及检查 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. 右键点击占用要跟踪的内存块的变量,然后选择Add Watchpoint

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

    使用以下选项配置您的监视点:

    • Enabled: 如果您想让 Android Studio 忽略监视点,直到您更改设置,请取消选择此选项。Android Studio 会保存您的监视点,以便您以后可以访问它。
    • Suspend: 默认情况下,当 Android 系统访问您分配给监视点的内存块时,它会暂停您的应用进程。如果您不希望出现此行为,请取消选择此选项。这将显示您可以用于自定义系统与您的监视点交互时的行为的其他选项:Log message to consoleRemove when hit
    • Access Type: 选择您的应用在尝试读取写入系统分配给变量的内存块时是否应触发您的监视点。要在读取或写入时触发您的监视点,请选择Any
  4. 点击Done

要查看所有监视点并配置监视点设置,请点击调试窗口中的View Breakpoints 。断点对话框随即显示,如图 10 所示。

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

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

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

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

在调试模式下,您可以查看资源值并为 Java 或 Kotlin 代码中的变量选择不同的显示格式。在显示“变量”标签页并选择帧的情况下,执行以下操作:

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

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

    • Class: 显示类定义。
    • toString: 显示字符串格式。
    • Object: 显示对象(类的实例)定义。
    • Array: 以数组格式显示。
    • Timestamp: 以 yyyy-mm-dd hh:mm:ss 格式显示日期和时间。
    • Auto: Android Studio 根据数据类型选择最佳格式。
    • Binary: 使用零和一显示二进制值。
    • MeasureSpec: 从父级传递给所选子级的值。请参阅 MeasureSpec
    • Hex: 以十六进制值显示。
    • Primitive: 作为使用原始数据类型的数值显示。
    • Integer:Integer 类型的数值显示。

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

  1. 右键点击资源值。
  2. 选择View as
  3. 选择Create
  4. 此时将显示Java Data Type Renderers对话框。请按照 Java Data type renderers 中的说明进行操作。