使用 Jack 编译

Jack 是一种 Android 工具链,用于将 Java 源代码编译成 Android dex 字节码。它取代了之前由 javac、ProGuard、jarjar 和 dx 等多种工具组成的 Android 工具链。由于 Jack 是 Android 6.x 的默认 Android 编译工具链,因此要使用 Jack,您只需使用标准的 Makefile 命令来编译源代码树或您的项目即可,无需进行其他任何操作。

Jack 简介

Jack 工具链具有以下优势:

Jack 概览
图 1. Jack 概览。
  • 完全开放的源代码:Jack 是在 AOSP 中提供的;我们欢迎用户贡献内容。
  • 提高编译速度:Jack 能够利用 dex 预处理、增量编译和 Jack 编译服务器来缩短编译时间。
  • 能够处理以下任务:压缩、混淆、重新打包和多 dex 处理。不再需要使用单独的软件包(如 ProGuard)。

从 Android 7.0 起,Jack 支持使用 JaCoCo 衡量代码覆盖率。如需了解详情,请参阅使用 JaCoCo 衡量代码覆盖率Java 8 语言功能

Jack 库格式

Jack 具有自己的 .jack 文件格式,其中包含相应库的预编译 dex 代码,可实现更快速的编译(dex 预处理)。

Jack 库文件内容
图 2. Jack 库文件内容。

Jill

Jill 工具可将现有的 .jar 库转换为新的库格式,如下图所示。

使用 Jill 导入 .jar 库
图 3. 导入现有 .jar 库的工作流程。

Jack 编译服务器

首次使用 Jack 时,它会在您的计算机上启动一个本地 Jack 编译服务器。该服务器:

  • 能够实现内在加速,因为它可以避免在每次编译时都启动新的主机 JRE JVM、加载 Jack 代码、初始化 Jack 以及准备 JIT。此外,它还会在小规模编译期间(例如增量模式下)实现非常快的编译速度。
  • 是短期内控制并行 Jack 编译数量的解决方案。该服务器可以避免计算机过载(内存或磁盘问题),因为它会限制并行编译的数量。

如果没有任何编译工作,在空闲一段时间之后,Jack 服务器会自行关闭。它使用 localhost 接口上的两个 TCP 端口,因此无法从外部访问。您可以通过修改 $HOME/.jack 文件来修改所有参数(并行编译的数量、超时、端口号等)。

$HOME/.jack 文件

$HOME/.jack 文件包含以下针对 Jack 服务器变量的设置,采用纯 bash 语法:

  • SERVER=true:启用 Jack 的服务器功能。
  • SERVER_PORT_SERVICE=8072:设置该服务器上用于编译的 TCP 端口号。
  • SERVER_PORT_ADMIN=8073:设置该服务器上用于管理的 TCP 端口号。
  • SERVER_COUNT=1:未使用。
  • SERVER_NB_COMPILE=4:设置允许的最大并行编译数量。
  • SERVER_TIMEOUT=60:设置无编译工作时服务器在自行关闭之前必须等待的空闲秒数。
  • SERVER_LOG=${SERVER_LOG:=$SERVER_DIR/jack-$SERVER_PORT_SERVICE.log}:设置服务器日志写入到的文件。默认情况下,此变量可被环境变量重载。
  • JACK_VM_COMMAND=${JACK_VM_COMMAND:=java}:设置用于在主机上启动 JVM 的默认命令。默认情况下,此变量可被环境变量重载。

Jack 编译问题排查

问题 措施
您的计算机在编译期间无响应,或者 Jack 编译因“Out of memory error”(内存不足错误)而失败 您可以通过修改 $HOME/.jack 并将 SERVER_NB_COMPILE 改为较低的值来减少同时进行的 Jack 编译的数量,从而改善这种情况。
编译因“Cannot launch background server”(无法启动后台服务器)而失败 最可能的原因是您计算机上的 TCP 端口都被占用了。您可以通过修改 $HOME/.jackSERVER_PORT_SERVICESERVER_PORT_ADMIN 变量)来更改端口。

如果上述方法无法解决问题,请报告该错误(务必附上您的编译日志和 Jack 服务器日志)。要解决这种情况,请通过修改 $HOME/.jack 并将 SERVER 更改为 false 来停用 Jack 编译服务器。遗憾的是,这将大大降低编译速度,并可能会迫使您使用加载控制(make 的选项 -l )启动 make -j
编译卡住了,没有任何进展 报告并提供以下信息(如果可能):
  • 卡住时所在的命令行。
  • 此命令行的输出。
  • 执行 jack-admin server-stat 获得的结果。
  • $HOME/.jack 文件。
  • Jack 服务器日志(已转储服务器状态)的内容。要获取服务器日志,请执行以下操作:
    • 通过运行 jack-admin list-server 查找 Jack 后台服务器进程。
    • 向该服务器发送 kill -3 命令,将其状态转储到日志文件中。
  • 执行 ls -lR $TMPDIR/jack-$USER 获得的结果。
  • 运行 ps j -U $USER 获得的结果。
要解决这种情况,请使用 jack-admin kill-server 停止 Jack 后台服务器,然后移除临时目录(/tmp$TMPDIR)的 jack-$USER 中包含的临时目录。
其他问题 要报告错误或请求功能,请使用 http://b.android.com 中的公开问题跟踪工具。使用 Jack 工具错误报告Jack 工具功能请求模板,并记得将 Jack 日志附到错误报告中。

查找 Jack 日志

如果您曾针对 dist 目标运行 make 命令,则 Jack 日志位于 $ANDROID_BUILD_TOP/out/dist/logs/jack-server.log 中。如果没有,则您可以通过运行 jack-admin server-log 找到该日志。对于可重现的 Jack 错误,您可以通过设置以下变量来获取更详细的日志:

export ANDROID_JACK_EXTRA_ARGS="--verbose debug --sanity-checks on -D sched.runner=single-threaded"

使用标准 Makefile 命令编译源代码树(或您的项目),并附上标准输出和错误。要移除详细的编译日志,请运行以下命令:

unset ANDROID_JACK_EXTRA_ARGS

Jack 的使用限制

  • 默认情况下,Jack 服务器为单用户模式,一台计算机上只能有一位用户使用。要支持更多用户,请为每位用户选择不同的端口号,并相应地调整 SERVER_NB_COMPILE。您还可以通过在 $HOME/.jack 中设置 SERVER=false 来停用 Jack 服务器。
  • 当前的 vm-tests-tf 集成方案会导致 CTS 编译速度较慢。
  • 不支持字节码处理工具(如 JaCoCo)。

使用 Jack

Jack 支持 Java 编程语言 1.7,并集成了下述附加功能。

dex 预处理

在生成 Jack 库文件时,系统会生成该库的 .dex 文件并将其作为 dex 预处理文件存储在 .jack 库文件中。在进行编译时,Jack 会重复使用每个库中的 dex 预处理文件。所有库均经过 dex 预处理:

包含 dex 预处理文件的 Jack 库
图 4. 包含 dex 预处理文件的 Jack 库。

如果在编译过程中使用了压缩、混淆或重新打包功能,则 Jack 不会重复使用库的 dex 预处理文件。

增量编译

增量编译指的是,仅重新编译自上次编译后出现过更改的组件及其依赖项。当只有少数组件出现过更改时,进行增量编译可能比完整编译快得多。

增量编译默认处于未启用状态(当压缩、混淆、重新打包或旧版多 dex 处理功能启用后,增量编译会自动被停用)。要启用增量编译,请将以下行添加到您要进行增量编译的项目的 Android.mk 文件中:

LOCAL_JACK_ENABLED := incremental

压缩和混淆

Jack 使用 proguard 配置文件来实现压缩和混淆功能。

常用选项包括:

  • @
  • -include
  • -basedirectory
  • -injars
  • -outjars // only 1 output jar supported
  • -libraryjars
  • -keep
  • -keepclassmembers
  • -keepclasseswithmembers
  • -keepnames
  • -keepclassmembernames
  • -keepclasseswithmembernames
  • -printseeds

压缩选项包括:

  • -dontshrink

混淆选项包括:

  • -dontobfuscate
  • -printmapping
  • -applymapping
  • -obfuscationdictionary
  • -classobfuscationdictionary
  • -packageobfuscationdictionary
  • -useuniqueclassmembernames
  • -dontusemixedcaseclassnames
  • -keeppackagenames
  • -flattenpackagehierarchy
  • -repackageclasses
  • -keepattributes
  • -adaptclassstrings

忽略的选项包括:

  • -dontoptimize // Jack does not optimize
  • -dontpreverify // Jack does not preverify
  • -skipnonpubliclibraryclasses
  • -dontskipnonpubliclibraryclasses
  • -dontskipnonpubliclibraryclassmembers
  • -keepdirectories
  • -target
  • -forceprocessing
  • -printusage
  • -whyareyoukeeping
  • -optimizations
  • -optimizationpasses
  • -assumenosideeffects
  • -allowaccessmodification
  • -mergeinterfacesaggressively
  • -overloadaggressively
  • -microedition
  • -verbose
  • -dontnote
  • -dontwarn
  • -ignorewarnings
  • -printconfiguration
  • -dump

重新打包

Jack 使用 jarjar 配置文件来进行重新打包。虽然 Jack 与“rule”规则类型兼容,但与“zap”或“keep”规则类型不兼容。如果您需要使用“zap”或“keep”规则类型,请提交功能请求,并在其中说明您在应用中如何使用该功能。

多 dex 处理支持

Jack 支持本地多 dex 处理和旧版多 dex 处理。由于 dex 文件的方法数上限为 65K,因此方法数超过 65K 的应用必须拆分成多个 dex 文件。有关详情,请参阅构建方法数超过 65K 的应用