It's happening now, watch the livestream.

d8

d8 是一种命令行工具,Android Studio 和 Android Gradle 插件可使用该工具将您项目的 Java 字节码编译为在 Android 设备上运行的 DEX 字节码,这可让您在应用代码中使用 Java 8 语言功能。

d8 还作为独立工具包含在 Android 编译工具 28.0.1 及更高版本中:android_sdk/build-tools/version/

一般用法

d8 是一个简单易用的工具,您只需提供指向要转换为 DEX 字节码的已编译 Java 字节码的路径即可,如下所示。

    d8 MyProject/app/build/intermediates/classes/debug/*/*.class
    

输入字节码可以是 *.class 文件或容器(例如 JAR、APK 或 ZIP 文件)的任意组合。您还可以添加 DEX 文件作为 d8 的输入,以将这些文件合并到 DEX 输出中,这在纳入增量编译的输出时很有用。

默认情况下,d8 会将 Java 字节码编译为优化的 DEX 文件,并在其中包含相关的调试信息,以供您在运行时用于调试代码。然而,您也可以添加可选标记来执行各种操作,例如执行增量编译、指定应编译到主 DEX 文件中的类,以及指定使用 Java 8 语言功能所需的其他资源对应的路径。

    d8 path-to-input-files [options]
    

下表介绍了可与 d8 一起使用的可选标记。

选项 说明
--debug 编译 DEX 字节码并在其中包含调试信息,例如调试符号表。

此选项默认处于启用状态。要在 DEX 字节码中包含调试信息,d8 要求输入 Java 字节码中包含此信息。例如,如果您使用 javac 编译代码,则需要传递 -g 标记,以在输出的 Java 字节码中包含调试信息。

当为应用或库的发布版本编译 DEX 文件时,请改用下述 --release 标记。

--release 编译 DEX 字节码,而不包含调试信息。不过,d8 包含了在生成堆栈轨迹和记录异常时需要使用的一些信息。

在为公开发布版本编译字节码时,应该传递此标记。

--output path 指定所需的 DEX 输出路径。默认情况下,d8 会将 DEX 文件输出到当前工作目录中。

如果您指定 ZIP 或 JAR 文件的路径和名称,则 d8 会创建指定的文件并将 DEX 输出文件包含在其中。如果指定现有目录的路径,则 d8 会将 DEX 文件输出到该目录中。

--lib android_sdk/platforms/api-level/android.jar 指定 Android SDK 的 android.jar 的路径。编译使用 Java 8 语言功能的字节码时需要使用此标记。
--classpath path 指定 d8 在编译项目的 DEX 文件时可能需要使用的类路径资源。特别是在编译使用 Java 8 语言功能的字节码时,d8 会要求您指定特定的资源。
--min-api number 指定您希望 DEX 输出文件支持的最低 API 级别。
--intermediate 传递此标记,可告知 d8 您并未编译项目的全部 Java 字节码。此标记在执行增量编译(而不是编译您想在设备上运行的优化 DEX 文件)时非常有用,d8 会创建中间 DEX 文件,并将其存储在指定的输出或默认路径中。

如果要编译要在设备上运行的 DEX 文件,请不要使用此标记,并指定中间 DEX 类的路径作为输入。

--file-per-class 将每个类编译到单独的 DEX 文件中。

启用此标记后,您只需重新编译已更改的类,从而执行增量编译。使用 Android Gradle 插件执行增量编译时,此优化默认处于启用状态。

如果您还指定了 --main-dex-list,则无法使用此标记。

--no-desugaring 停用 Java 8 语言功能。仅当您不想编译使用 Java 8 语言功能的 Java 字节码时,才可使用此标记。
--main-dex-list path 指定列出 d8 应包含在主 DEX 文件中的类的文本文件,该文件的名称通常为 classes.dex。也就是说,如果您不使用此标记指定类列表,d8 将无法保证主 DEX 文件中会包含哪些类。

由于 Android 系统在启动您的应用时会先加载主 DEX 文件,因此您可以利用此标记将特定的类编译到主 DEX 文件中,从而使它们在应用启动时得到优先加载。这在支持旧版 multidex 时特别有用,因为在加载旧版 multidex 库之前,只有主 DEX 文件中的类在运行时可用。

请记住,该主 DEX 文件仍须满足 64K 引用限制。因此,请确保不要为主 DEX 文件指定太多类,否则会出现编译错误。默认情况下,在使用 --main-dex-list 指定类时,d8 只会包含主 DEX 文件中的类,这是为了便于调试与主 DEX 文件中缺少的类相关的问题。如果您指定的是 --release 模式,则 d8 会尝试在主 DEX 文件中包含尽可能多的其他类(直到达到 64K 限制为止),从而减少打包到应用发布版本中的 DEX 文件的数量。

如果您还指定了 --file-per-class,则无法使用此标记。

--version 输出您当前使用的 d8 版本。
--help 输出与使用 d8 有关的帮助文本。

执行增量编译

为了在开发过程中提高编译速度(例如提高持续集成编译的速度),您可以指示 d8 仅编译项目的部分 Java 字节码。例如,如果您启用了按类 dexing 处理,则只需重新编译自上次编译以来修改过的类。

以下命令可以执行几个类的增量编译,并启用按类 dexing 处理。该命令还指定了增量编译的输出目录。

    d8 MainActivity.class R.class --intermediate --file-per-class --output ~/build/intermediate/dex
    

d8 在执行增量编译时,会将一些额外的信息存储在 DEX 输出中,等以后完整编译您的应用时,它会利用这些信息正确地处理 --main-dex-list 选项以及合并 DEX 文件。例如,d8 在处理 Java 8 lambda 类时,会记录为每个输入类创建的 lamdba 类。在完整编译过程中,当 d8 在主 DEX 文件中包含某个类时,它会查询元数据,以确保为此类创建的所有 lambda 类也包含在主 DEX 文件中。

如果您已在多次增量编译中将项目的所有字节码编译到 DEX 文件中,则可以通过将中间 DEX 文件的目录传递给 d8 来执行完整编译,具体如下所示。此外,您也可以使用 --main-dex-list 指定您想让 d8 编译到主 DEX 文件中的类。由于输入是已编译为 DEX 字节码的一组文件,因此,完成此编译的速度应该比完成干净编译更快。

    d8 ~/build/intermediate/dex --release --main-dex-list ~/build/classes.txt --output ~/build/release/dex
    

编译使用了 Java 8 语言功能的字节码

d8 通过一个叫做“脱糖”的编译进程,使您能够在代码中使用 Java 8 语言功能,此进程会将这些实用的语言功能转换为可以在 Android 平台上运行的字节码。

Android Studio 和 Android Gradle 插件包含了 d8 为您启用脱糖所需的类路径资源。不过,从命令行使用 d8 时,您需要手动添加这些资源。

其中一个资源就是您的目标 Android SDK 中的 android.jar。此资源包含一组 Android 平台 API,您可以使用 --lib 标记来指定该资源的路径。

另一个资源是您项目的部分已编译的 Java 字节码,您目前不打算将这部分字节码编译为 DEX 字节码,但在将其他类编译为 DEX 字节码时需要用到这些字节码。例如,如果您的代码使用默认和静态接口方法(一种 Java 8 语言功能),则您需要使用此标记来指定您项目的所有 Java 字节码的路径,即使您不打算将所有 Java 字节码都编译为 DEX 字节码也是如此。这是因为 d8 需要根据这些信息来理解您项目的代码并解析对接口方法的调用。

以下示例代码对一个访问默认接口方法的类执行增量编译:

    d8 MainActivity.class --intermediate --file-per-class --output ~/build/intermediate/dex
    --lib android_sdk/platforms/api-level/android.jar
    --classpath ~/build/javac/debug