The Android Developer Challenge is back! Submit your idea before December 2.

调试应用

Android Studio 提供了一个调试程序,用来执行以下操作以及更多操作:

  • 选择要在哪个设备上调试应用。
  • 在 Java、Kotlin 和 C/C++ 代码中设置断点。
  • 在运行时检查变量和对表达式求值。

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

启用调试功能

您需要先按以下方式做好准备,然后才能开始调试:

  • 在设备上启用调试功能:

    如果您使用的是模拟器,则默认情况下会启用此功能。但是,对于连接的设备,您需要在设备开发者选项中启用调试功能

  • 运行可调试的编译变体:

    您必须使用在编译配置中包含 debuggable true编译变体。通常,您只需选择每个 Android Studio 项目中都包含的默认“调试”变体(即使它在 build.gradle 文件中不可见)。但是,如果您定义应该可调试的新编译类型,则必须将“debuggable true”添加到该编译类型:

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

    此属性也适用于包含 C/C++ 代码的模块。(不再使用 jniDebuggable 属性。)

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

开始调试

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

  1. 在应用代码中设置一些断点
  2. 在工具栏中,从目标设备下拉菜单中选择要用来调试应用的设备。

    目标设备下拉菜单。

    如果您未配置任何设备,那么您需要通过 USB 连接设备创建 AVD,才能使用 Android 模拟器

  3. 在工具栏中,点击 Debug

    如果您看到一个对话框询问您是否要“switch from Run to Debug”,这表示您的应用已在设备上运行,并且它将重启以便开始调试。如果您希望让同一应用实例保持运行状态,请点击 Cancel Debug,然后将调试程序连接到正在运行的应用

    否则,Android Studio 会编译一个 APK,使用调试密钥为其签名,将其安装在您选择的设备上,然后运行它。如果您向项目添加 C 和 C++ 代码,Android Studio 还会在 Debug 窗口中运行 LLDB 调试程序来调试您的原生代码。

  4. 如果 Debug 窗口未打开,请依次选择 View > Tool Windows > Debug(或点击工具窗口栏中的 Debug 图标 ),然后点击 Debugger 标签,如图 1 所示。

    图 1 Debugger 窗口,显示了当前线程和变量的对象树

将调试程序连接到正在运行的应用

如果您的应用已在设备上运行,则无需重启应用即可开始调试,具体操作步骤如下:

  1. 点击 Attach debugger to Android process 图标
  2. Choose Process 对话框中,选择要将调试程序连接到的进程。

    如果您使用的是模拟器或已取得 root 权限的设备,则可以选中 Show all processes 以查看所有进程。

    Debugger 下拉菜单中,您可以选择其他调试类型。默认情况下,Android Studio 会使用 Auto 调试类型来选择最适合您的调试程序选项,具体取决于您的项目是否包含 Java 或 C/C++ 代码。

  3. 点击 OK

    此时将显示 Debug 窗口。

注意:Android Studio 调试程序与垃圾回收器采用松散集成。Android 虚拟机可保证在调试程序断开连接之前不会对调试程序发现的任何对象进行垃圾回收。这可能会导致在调试程序处于连接状态时,对象随时间的推移逐渐积累起来。例如,如果调试程序发现了某个正在运行的线程,那么在调试程序断开连接之前,不会对关联的 Thread 对象进行垃圾回收,即使该线程已终止。

更改调试程序类型

由于调试 Java/Kotlin 代码和 C/C++ 代码需要不同的调试程序工具,因此 Android Studio 调试程序允许您选择要使用的调试程序类型。默认情况下,Android Studio 根据在项目中检测到的语言(使用 Auto 调试程序类型)决定要使用的调试程序。不过,您可以在调试配置(依次点击 Run > Edit Configurations)中或在您依次点击 Run > Attach debugger to Android process 后显示的对话框中手动选择调试程序。

可用的调试类型包括:

  • Auto:如果您希望 Android Studio 自动为您要调试的代码选择最合适的选项,请选择此类型。例如,如果您的项目包含任何 C 或 C++ 代码,Android Studio 会自动使用 Dual 调试类型。否则,Android Studio 会使用 Java 调试类型。
  • Java:如果您只想调试使用 Java 或 Kotlin 编写的代码,请选择此类型。Java 调试程序会忽略您在原生代码中设置的任何断点或观察点。
  • Native:(仅适用于 C/C++ 代码。)如果您只想使用 LLDB 调试代码,请选择此类型。使用此调试类型时,Java 调试程序会话视图不可用。默认情况下,LLDB 只检查您的原生代码,而会忽略 Java 代码中的断点。如果您也想调试 Java 代码,则应切换到 AutoDual 调试类型。

    注意:在 Android Studio 3.0 及更高版本中,原生调试在 32 位 Windows 系统上不起作用。如果您使用的是 32 位 Windows 系统并且需要调试原生代码,则应使用 Android Studio 2.3

  • Dual:(仅适用于 C/C++ 代码。)如果您想在调试 Java 代码与调试原生代码之间切换,请选择此类型。Android Studio 会将 Java 调试程序和 LLDB 都连接到您的应用进程,一个用于 Java 调试程序,一个用于 LLDB,这样一来,您不必重启应用或更改调试配置,便可同时对 Java 代码和原生代码中的断点进行检查。

    在图 2 中,请注意 Debug 窗口标题右侧的两个标签。由于应用同时包含 Java 代码和 C++ 代码,因此一个标签用于调试原生代码,另一个用于调试 Java 代码(由 -java 来指示)。

    图 2. 用于调试原生代码的标签和用于调试 Java 代码的标签

注意:如果要调试由编译器优化的本机代码,则可能会收到以下警告消息:This function was compiled with optimizations enabled. Some debugger features may not be available。使用优化标记(如 -O 标记)时,编译器会对编译的代码进行更改,以使其更高效地运行。这可能会导致调试程序报告意外或不正确的信息,因为调试程序很难将优化的编译代码映射回原始源代码。因此,您应该在调试原生代码时停用编译器优化。

使用系统日志

系统日志会在您调试应用时显示系统消息。这些消息包括运行在设备上的应用产生的信息。如果您要使用系统日志调试应用,请确保您的代码在应用处于开发阶段时写入日志消息并输出有关异常的堆栈轨迹。

在代码中写入日志消息

要在代码中写入日志消息,请使用 Log 类。日志消息会在您与应用交互时收集系统调试输出,从而帮助您了解执行流程。日志消息可以告诉您应用的哪个部分出现了故障。如需详细了解日志记录,请参阅写入和查看日志

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

Kotlin

    import android.util.Log
    ...
    private val TAG: String = MyActivity::class.java.simpleName
    ...
    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 */
            }
        }
    }
    

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 窗口中查看和过滤调试消息及其他系统消息。例如,您可以在进行垃圾回收时看到消息,或看到您使用 Log 类添加到应用的消息。

要使用 logcat,请开始调试并选择底部工具栏中的 Logcat 标签,如图 3 所示。

图 3. 包含过滤器设置的 Logcat 窗口

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

使用断点

Android Studio 支持使用若干类型的断点来触发不同的调试操作。最常见的类型是行断点,用于在指定的代码行暂停应用的执行。暂停时,您可以检查变量,对表达式求值,然后继续逐行执行,以确定运行时错误的原因。

要添加行断点,请按以下步骤操作:

  1. 找到您要暂停执行的代码行,然后点击该代码行的左侧空白处,或将光标置于该行上并按 Ctrl+F8(在 Mac 上,按 Command+F8)。
  2. 如果您的应用已在运行,您不必更新应用便可添加断点 - 只需点击 Attach debugger to Android proccess 图标 。否则,请点击 Debug 图标 以开始调试。

图 3. 当您设置断点时,相应的代码行旁边会出现一个红点

当您的代码执行到达该断点时,Android Studio 会暂停应用的执行。您随后可以使用 Debugger 标签中的工具来确定应用的状态:

  • 要检查变量的对象树,请在 Variables 视图中将其展开。如果 Variables 视图不可见,请点击 Restore Variables View 图标

  • 要在当前执行点对某个表达式求值,点击 Evaluate Expression

  • 要前进到下一行代码(而不进入方法),点击 Step Over

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

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

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

如果您的项目使用了任何原生代码,默认情况下,Auto 调试类型会将 Java 调试程序和 LLDB 作为两个单独的进程连接到您的应用,这样一来,您无需重启应用或更改设置,便可在检查 Java 断点与 C/C++ 断点之间进行切换。

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

当 Android Studio 将应用部署到目标设备时,系统会打开 Debug 窗口,在该窗口中为每个调试程序进程显示一个标签或调试会话视图,如图 4 所示。

图 4. 使用 LLDB 调试原生代码

  1. 当 LLDB 调试程序遇到 C/C++ 代码中的断点时,Android Studio 会切换到 <your-module> 标签。还会显示 FramesVariablesWatches 窗格,其作用与您调试 Java 代码时完全相同。虽然 LLDB 会话视图中未出现 Threads 窗格,但您可以使用 Frames 窗格中的下拉列表访问您的应用进程。您可以在有关如何调试窗口框架检查变量的部分中详细了解这些窗格。

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

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

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

查看和配置断点

要查看所有断点并配置断点设置,请点击 Debug 窗口左侧的 View Breakpoints 。此时将显示 Breakpoints 窗口,如图 5 所示。

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

您可以通过 Breakpoints 窗口左侧的列表启用或停用每个断点。如果停用了某个断点,当您的应用遇到该断点时,Android Studio 不会暂停应用。从列表中选择某个断点即可配置其设置。您可以将某个断点配置为最初处于停用状态,让系统在遇到其他断点时将其启用。您还可以配置在遇到某个断点后是否应将其停用。要为任何异常设置断点,请在断点列表中选择 Exception Breakpoints

调试窗口框架

Debugger 窗口的 Frames 窗格中,您可以检查导致遇到当前断点的堆栈帧。这样,您就可以浏览和检查堆栈帧,同时还可以检查 Android 应用中的线程列表。要选择线程,请使用线程选择器下拉列表并查看其堆栈帧。点击帧中的元素会在编辑器中打开源代码。您还可以按窗口框架指南中的说明自定义线程呈现和导出堆栈帧。

检查变量

系统将您的应用停止在某个断点处并且您从 Frames 窗格中选择某个框架后,您可以在 Debugger 窗口的 Variables 窗格中检查变量。此外,您还可以在 Variables 窗格中使用所选框架内提供的静态方法和/或变量对临时表达式求值。

Watches 窗格提供的功能类似,不同的是添加到 Watches 窗格的表达式可跨调试会话存留。您应该为自己经常访问或者提供的状态对当前调试会话有帮助的变量和字段添加观察点。VariablesWatches 窗格如图 6 所示。

要向 Watches 列表中添加变量或表达式,请按以下步骤操作:

  1. 开始调试。
  2. Watches 窗格中,点击 Add 图标
  3. 在随后显示的文本框中,输入您要观察的变量或表达式的名称,然后按 Enter 键。

要从 Watches 列表中移除某一项,请选择该项,然后点击 Remove

选择某一项,然后点击 Up Down ,可对 Watches 列表中的元素重新排序。

图 6. Debugger 窗口中的 Variables 和 Watches 窗格

添加观察点

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

在 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 目标设备上最多只支持四个观察点。其他设备支持的观察点数量可能更少。

如果您满足上述要求,便可以添加观察点,具体操作步骤如下:

  1. 在应用暂停于某个断点处的情况下,转到 LLDB 会话视图中的 Variables 窗格。
  2. 右键点击占用您要跟踪的内存块的变量,然后选择 Add Watchpoint。此时将显示一个用来配置观察点的对话框,如图 7 所示。

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

  3. 使用以下选项配置观察点:
    • Enabled:如果您要告知 Android Studio 暂时忽略相应的观察点,可以取消选中此选项。Android Studio 仍会保存您的观察点,以便您稍后在调试会话中访问。
    • Suspend:默认情况下,Android 系统会在其访问您分配给观察点的内存块时暂停应用进程。如果您不希望出现此行为,可以取消选中此选项。这样会显示一些附加选项,即 Log message to consoleRemove [the watchpoint] when hit,您可以使用这些选项自定义系统与观察点进行交互时的行为。
    • Access Type:选择当您的应用尝试对系统分配给变量的内存块执行读取写入操作时是否应触发您的观察点。要在执行读取或写入操作时触发您的观察点,请选择 Any
  4. 点击完成

要查看所有观察点并配置观察点设置,请点击 Debug 窗口左侧的 View Breakpoints 图标 。此时将显示 Breakpoints 对话框,如图 8 所示。

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

添加观察点后,点击 Debug 窗口左侧的 Resume Program 图标 可继续执行应用进程。默认情况下,如果您的应用尝试访问您设置了观察点的内存块,Android 系统会暂停您的应用进程,您的应用最后执行的那行代码旁边会出现一个观察点图标 ,如图 9 所示。

图 9. Android Studio 会指示应用在即将触发观察点之前执行的那行代码

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

在调试模式下,您可以查看 Java 代码中的变量的资源值并为其选择其他显示格式。在显示了 Variables 标签并选择了框架的情况下,执行以下操作:

  1. Variables 列表中,右键点击资源行的任意位置以显示下拉列表。
  2. 在下拉列表中选择 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 类型的数值。

您可以按以下步骤创建自定义格式(数据类型渲染程序):

  1. 右键点击资源值。
  2. 选择 View as
  3. 选择 Create。此时将显示 Java Data Type Renderers 对话框。
  4. 按照 Java 数据类型渲染程序中的说明进行操作。