启用调试

Detect Automatically(自动检测)

如果您希望 Android Studio 自动为您正在调试的代码选择最佳选项,请选择此调试类型。例如,如果您的项目中包含任何 C 或 C++ 代码,Android Studio 会自动使用“Dual”调试类型。否则,Android Studio 会使用“Java-Only”调试类型。

Java Only(仅 Java)

如果您只想调试用 Java 或 Kotlin 编写的代码,请选择此调试类型。“Java-Only”调试器会忽略您在原生代码中设置的任何断点或监视。

Native Only(仅原生 - 仅适用于 C/C++ 代码)

如果您只想使用 LLDB 调试您的代码,请选择此调试类型。使用此调试类型时,Java 调试器会话视图不可用。默认情况下,LLDB 仅检查您的原生代码并忽略 Java 代码中的断点。如果您还需要调试 Java 代码,请切换到“Detect Automatically”或“Dual”调试类型。

原生调试仅在满足以下要求的设备上有效:

设备支持 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,该命令将打印值 0 或 unknown key 错误。如果未启用 ptrace,它将打印除 0 以外的值。

Dual (Java + Native)(双重 - 仅适用于 C/C++ 代码)

如果您想在调试 Java 代码和原生代码之间切换,请选择此调试类型。Android Studio 会将 Java 调试器和 LLDB 同时附加到您的应用进程,这样您无需重启应用或更改调试配置,即可检查 Java 代码和原生代码中的断点。

在图 2 中,请注意“Debug”窗口标题右侧的两个选项卡。由于应用同时包含 Java 和 C++ 代码,其中一个选项卡用于调试原生代码,另一个用于调试 Java 代码(如 -java 所示)。

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

注意:在调试由编译器优化的原生代码时,您可能会收到以下警告消息:This function was compiled with optimizations enabled. Some debugger features may not be available。使用优化标志时,编译器会对编译后的代码进行更改以使其运行更高效。这可能导致调试器报告意外或错误的信息,因为调试器很难将优化后的编译代码映射回原始源代码。因此,您应在调试原生代码时禁用编译器优化。

使用系统日志

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

在代码中写入日志消息

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

以下示例显示了如何添加日志消息以确定您的 Activity 启动时是否有可用的先前状态信息:

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 而不挂起执行。这有助于避免代码中充斥着日志语句。

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

找到要暂停执行的代码行。

点击该行代码左侧的装订线,或将光标置于该行并按 Control+F8(在 macOS 上为 Command+F8)。

如果您的应用已在运行,请点击 Attach debugger to Android process 。否则,要开始调试,请点击 Debug 。

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

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

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

要识别应用的状态,请使用 Debugger 选项卡中的工具:

要检查变量的对象树,请在 Variables 视图中展开它。如果 Variables 视图不可见,请点击 Layout Settings 并确保已勾选 variables。

要进入代码的下一行而不进入方法内部,请点击 Step Over 。

要进入方法调用内部的第一行,请点击 Step Into 。

要跳出当前方法并进入下一行,请点击 Step Out 。

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

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

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

当 Android Studio 将应用部署到目标设备时,“Debug”窗口会为每个调试器进程打开一个选项卡或调试会话视图,如图 6 所示。

图 6. 使用 LLDB 调试原生代码。

当 LLDB 调试器在 C/C++ 代码中遇到断点时,Android Studio 会切换到 选项卡。Frames、Variables 和 Watches 窗格也可用,其工作方式与调试 Java 代码时完全相同。

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

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

当 Java 调试器在 Java 或 Kotlin 代码中遇到断点时,Android Studio 会切换到 -java 选项卡。

在通过 LLDB 调试时,您可以使用 LLDB 会话视图中的 LLDB 终端向 LLDB 传递命令行选项。如果您有一些希望 LLDB 在每次启动调试时执行的特定命令(在调试器附加到应用进程之前或之后),您可以将这些命令添加到调试配置中。

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

查看和配置断点

要查看所有断点并配置断点设置,请点击“Debug”窗口中的 View Breakpoints 。此时会出现 Breakpoints 窗口,如图 7 所示。

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

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

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

要暂时禁用所有断点,请点击“Debug”窗口中的 Mute Breakpoints 。再次点击可重新启用。

调试窗口帧

在 Debugger 窗口中,Frames 窗格允许您检查导致当前断点被命中的堆栈帧。这使您可以导航和检查堆栈帧,还可以检查 Android 应用中的线程列表。

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

检查变量

在 Debugger 窗口中,当系统在断点处停止您的应用且您从 Frames 窗格中选择了一个帧时,Variables 窗格允许您检查变量。Variables 窗格还允许您使用所选帧中可用的静态方法和/或变量来计算临时表达式。

要向对象树添加表达式(在调试应用时):

图 8. “Debug”窗口中的对象树和表达式输入框。

输入要监视或显示的表达式。

点击 Add to watches 或按 Enter 键计算表达式。

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

当命中断点或您单步执行代码时,受监视的表达式会更新。

计算出的表达式将保留在对象树的顶部,直到您手动计算另一个表达式或单步执行代码。

要从对象树中移除受监视的表达式,请右键点击该表达式,然后点击 Remove Watch。

添加观察点

在调试 C/C++ 代码时,您可以设置称为观察点 (watchpoints) 的特殊类型断点,当您的应用与特定的内存块交互时,它可以挂起应用进程。例如,如果您设置了两个指向同一内存块的指针并为其分配了一个观察点,使用任一指针访问该内存块都会触发观察点。

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

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

要设置观察点,必须满足以下要求:

您的目标物理设备或模拟器使用 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 版本中修复。

如果您满足要求,可以按如下方式添加观察点:

当应用在断点处挂起时,导航到 LLDB 会话视图中的 Variables 窗格。

右键点击占用您要跟踪的内存块的变量,然后选择 Add Watchpoint。

图 9. 向内存中的变量添加观察点。

此时将出现配置观察点的对话框,如图 9 所示。

使用以下选项配置您的观察点:

Enabled: 如果您想让 Android Studio 忽略该观察点直到您更改设置,请取消选中此选项。Android Studio 会保存您的观察点,以便您稍后访问。

Suspend: 默认情况下,Android 系统会在应用访问您分配给观察点的内存块时挂起应用进程。如果您不想要此行为,请取消选中此选项。这将显示更多可用于自定义系统与观察点交互时的行为的选项:Log message to console 和 Remove when hit。

Access Type: 选择应用在尝试 Read(读取)或 Write(写入)系统分配给变量的内存块时是否应触发观察点。要以读取或写入方式触发观察点,请选择 Any。

点击 Done。

要查看所有观察点并配置观察点设置,请点击“Debug”窗口中的 View Breakpoints 。此时会出现 Breakpoints 对话框,如图 10 所示。

图 10. Breakpoints 对话框列出了您当前的观察点,并包含每个观察点的行为设置。

添加观察点后,点击“Debug”窗口中的 Resume Program 以恢复您的应用进程。默认情况下,如果您的应用尝试访问您设置了观察点的内存块,Android 系统会挂起您的应用进程,并且观察点图标 会出现在应用最后执行的代码行旁边,如图 11 所示。

图 11. Android Studio 指示应用在触发观察点之前执行的代码行。

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

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

在 Variables 列表中,右键点击资源行的任意位置以显示列表。

在列表中,选择 View as,然后选择您要使用的格式。可用格式取决于您选择的资源的数据类型。您可能会看到以下一个或多个选项:

Class: 显示类定义。

toString: 显示字符串格式。

Object: 显示对象(类实例)定义。

Array: 以数组格式显示。

Timestamp: 显示日期和时间,格式为:yyyy-mm-dd hh:mm:ss。

Auto: Android Studio 根据数据类型选择最佳格式。

Binary: 使用 0 和 1 显示二进制值。

MeasureSpec: 从父级传递给选定子级的值。请参阅 MeasureSpec。

Hex: 显示为十六进制值。

Primitive: 使用原始数据类型显示为数值。

Integer: 显示为 Integer 类型的数值。

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

右键点击资源值。

选择 View as。

选择 Create。

Java Data Type Renderers 对话框将会显示。请按照 Java Data type renderers 中的说明进行操作。

[an error occurred while processing the directive]