Android 新一代编译 toolchain Jack & Jill 简介
转自:http://taobaofed.org/blog/2016/05/05/new-compiler-for-android/
2016 年 3 月 10 日, Google 向外界发布了 Android N 的预览版,并宣布了 Android N 的 Roadmap,Android N 的最终版源代码将于今年 8 或 9 月份释出到 AOSP 项目。
在众多的 Android N 新特性中,有一项新工具链的出现与 Android 生态圈的所有开发者息息相关,即 Jack & Jill 编译器的引入。
在依赖了 Sun/Oracle 的 Java 编译器十年之后,Android 终于有了自己的 Java 编译器。
本文试图对市面上非常有限的资料进行总结,向大家介绍 Jack & Jill 的缘起,工作方式和原理。
Jack 是 Java Android Compiler Kit 的缩写,它可以将 Java 代码直接编译为 Dalvik 字节码,并负责 Minification, Obfuscation, Repackaging, Multidexing, Incremental compilation。它试图取代 javac/dx/proguard/jarjar/multidex 库等工具。
- git 源代码地址是 https://android.googlesource.com/toolchain/jack。
Jill 是 Jack Intermediate Library Linker 的缩写,它负责 “Shielding JACK from Java byte code”;实际上辅助 Jack 对.class 做预处理,生成 .jack
文件
- git 源代码地址是 https://android.googlesource.com/toolchain/jill。
缘起
虽然 Google 是在宣布 Android N 预览版时隆重介绍了Jack & Jill。但是,早在 2014 年 Google 就对外宣布了新编译器 Jack 的存在 meet our new experimental toolchain, 它的开发启动时间更是远远早于 2014 年。
下面是我总结的 Jack 的缘起
- 一家名叫 FlexyCore 的小公司基于 GCC toolchain 开发了 Android 平台上的 AOT 编译器,被 Google 看中并于 2013 年被收购
- FlexyCore team 基于 LLVM toolchain 开发了 ART,并成为 Android 5.0 之后的缺省 Java Runtime
- FlexyCore team 基于 Eclipse ecj 编译器开始开发 Jack,基于 ASM4 开发 Jill。 他们早在 2014 年 2 月就开始提交 Jill 的代码了 Jill initial commit; 3 月份开始提交 Jack的代码 Jack initial commit
- 自 Android build-tools 21.1 开始,里面已经内置 jack.jar 和 jill.jar
- Android Gradle plugin 自 0.14 开始支持 Jack & Jill initial commit
- 自 Android 6.0 开始,Jack & Jill 成为 AOSP 的官方编译器, 也就是说所有的 Android 6.0 ROM 都是 Jack 编译出来的 link,也代表 Google 认为 Jack 达到了一定的成熟度
- 预计等 Android 7.0 正式发布时,Jack 可能会成为官方推荐的编译器
为什么要抛弃 Javac/dx,开发 Jack 和 Jill
据个人推测主要有三个目的
- 提高编译速度
- 应对 Oracle 的法律诉讼
- 将编译器掌控权拿在自己手中,不再受制于 Oracle,可以做一些 Android only 的优化
下面比较一下旧的 javac/dx/ProGuard/jarjar toolchain 和新的 Jack 编译器的工作流程
旧编译流程
简单的说,将 Java 代码和依赖库编译为 dex 有两个大的阶段
javac (.java –> .class) –> dx (.class –> .dex)
下面是用流程图表示的旧编译过程
- javac 将 java 代码编译为 java bytecode, 以
.class
的形式存在; 以 jar 和 aar 形式存在的依赖库,代码在里面以一堆.class 的形式存在 - Proguard 工具读取 Proguard 配置,对
.class
做 shrinking, obfuscation,输出 Proguard mapping - dx 将多个
.class
转化为单一的 classes.dex ; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex
新编译流程
新的编译过程只有一个阶段了,它完全抛弃了 javac, ProGuard, jarjar 等工具,一个工具搞定一切
Jack (.java –> .jack –> .dex)
下面是用流程图表示的 Jill 预处理过程
下面是用流程图表示的 Jack 编译过程
- 各种依赖库仍然以 jar/aar 的形式存在
- 辅助工具 Jill 将根据依赖库中的
.class
生成 Jayce 格式的 IL,并调用 Jack 做 pre-dex 并生成.jack
,此过程只在编译 app 时发生一次 - Jack 将 java 源代码也编译为
.jack
,然后将多个.jack
转化为单一的.dex
; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex
pre-dex 的详细解释可以参阅此链接 new-build-system
|
.Jack中间文件
.Jack
的具体格式如下图所示
可见里面包含了 Jayce 格式的 IL ,pre-dex,原始 aar 中的资源文件,以及 Jack 会用到的一些 meta 信息
下图简单比较了 java 代码转化的 .class
, Jayce IL 和 dex 的内容异同
简单比较下三种 IL 的区别:
Sun/Oracle Hotspot VM 是基于栈式的,所以 .class
文件的内容就是不断地压操作数到栈顶,从栈顶读取操作数,比较或做运算,将结果再压回栈顶
Dalvik VM 是基于寄存器的,所以 .dex
的内容就是不断地 move 操作数到寄存器,比较或做运算,将结果写回寄存器或内存地址
Jayce 则是 Jack&Jill 专有的 IL, 目前没有查阅到更多的官方资料。只能参阅 Jill 源代码中 com.android.jill.backend.jayce 包的代码了,比如其中的 Token 类就定义了 Jayce 的 Token 定义。
个人推测 Jayce 存在的意义是:
- 为了在整合多个 jack 文件,生成单一的 dex 时,方便 Jack 做一些全局性的后端编译优化。
- 从 Android 生态圈中完全去除 Oracle 的 Java Bytecode 格式
使用Jack编译器的优势
- 对依赖库做 pre dex,且成果会被保存到 build/intermediates/jill/debug 目录。
之后的编译过程中,只要依赖库的数目和版本不变,之前的 pre dex 成果会被复用;Jack 只需要编译变化的源代码,然后对多个 dex 进行 merge 即可,能够加速整个编译过程。
- 编译时会启动一个 Jack compilation server,并开启并行编译
Jack 文档是这么介绍的
|
- 支持 Java 8 的一部分特性
- Jack 由 Google 完全掌控,未来可能成为 Android sdk 的默认编译器
- 向后兼容到 Android 2.3
采用 Jack 对打包流程的影响
- 不再需要独立的 ProGuard。Jack 支持读取旧的 ProGuard 配置,完成 shrinking, obfuscation 的工作
- 不再需要独立的 jarjar。Jack 支持读取旧的 jarjar 配置,完成 repackaging 的工作
- 没有
.class
文件了,直接操纵或读取 Java 字节码的各种工具如 JaCoCo/Lint/Mokito/Retrolambda 没有了用武之地。但是仍然可以在 Android Library 上使用这些工具,编译为 aar/jar 后作为 Jill 的输入 - annotation processors 如 Dagger, ButterKife 仍可以使用
- Scala/Kotlin 等第三方 JVM 语言编写的内容必须先被 Jill 处理,再作为 Jack 的输入
Jack 当前的局限(截止到2016/03/15)
- 暂时还不支持 Android Studio 2.0 的 Instant Run 特性
- 暂时还不支持 data binding
65k 方法数目问题
为什么会有 65k 问题?
当你的 app 足够复杂之后,在打包时常常会遇到这种错误提示
|
为什么方法数目不能超过 65k 呢?有人说是 dexopt 的问题,有人说是 dex 格式的限制,下面我们看看这个 log 到底是哪里吐出来的,然后分析下具体原因。
- dex 格式的限制?
首先我们看一下 dex 的结构定义
|
可见 dex 文件结构是用 32 位来存储 method id 的,最大支持 2 的 32 次方,因此 65k 的原因不在于此。
- dexopt 的原因?
dexopt 是 app 已经打包成功,安装到手机之后才会发生的过程。但是 65k 问题是在打包时发生的,所以问题原因也不在此
一般提到的 dexopt 错误,其实是 Android 2.3 及其以下在 dexopt 执行时只分配 5M 内存,导致方法数目过多(数量不一定到 65k)时在 odex 过程中崩溃,官方称之为 Dalvik linearAlloc bug(Issue 22586) 。
另:这个 linearAlloc 的限制不仅存在于 dexopt 里,还在 dalvik rumtime 中存在……
以下链接详细解释了此问题:https://github.com/simpleton/dalvik_patch
- 错误 log 是哪里吐出来的?
|
通过查阅 dalvik-bytecode 可知,@CCCC 的范围必须在 0~65535 之间。
所以归根结底,65k 问题是因为 dalvik bytecode 中的指令格式使用了 16 位来放 @CCCC 导致的;所以,不仅 Method 数目不能超过 65k, Field 和 Class 数目也不能超过 65k。
为什么 jack 没有 65k 问题
前文已经很清楚地解释了 65k 问题的由来,可见只要 dalvik bytecode 指令格式不升级,65k 问题是逃不掉的。
Jack 官网对 65k 问题是这么说的:
|
所以,Jack 和旧工具链对 multidex 的支持方式是相同的
被 Jack 编译出来的 app 执行时也和以前一样
- 若是 dalvik 虚拟机,它只支持读取一个 classes.dex。而 multidex 解决方案会读取多个
.dex
,帮我们做 dex 数组合并 - 若是 art 虚拟机,它会扫描 classes.dex, classes1.dex…classesN.dex,调用 dex2oat 转化为单一的 oat
Jack 是怎么支持 Java 8 的?
以 lambda 表达式为例
|
会被转化为 anonymous classes
|
Jack当前支持的 Java 8 特性可参见 j8-jack。
如何在 Gradle 脚本中使用 Jack 编译器编译 app
想使用 Jack 和 Jill 需要指定你的 Build Tools version 是 21.1.0+, Gradle plugin version 是1.0.0+。
以下的配置是我个人测试通过的配置
使用 Android Gradle 插件 2.1.0-alpha2
- dependencies {
classpath ‘com.android.tools.build2.1.0-alpha2’
}
- dependencies {
使用以下版本的 sdk 和 build-tool
- compileSdkVersion ‘android-N’
buildToolsVersion ‘24.0.0 rc1’
- compileSdkVersion ‘android-N’
在 defaultConfig 中指定用 Jack
- defaultConfig {
jackOptions {
enabled true
}
}
- defaultConfig {
使用 gradle 2.10 以上
- distributionUrl=http\://mirrors.taobao.net/mirror/gradle/gradle-2.10-bin.zip
- distributionUrl=http\://mirrors.taobao.net/mirror/gradle/gradle-2.10-bin.zip
- 使用 Android Studio 2.1 (preview) 或者命令行编译
可能需要提升 javaMaxHeapSize
- dexOptions{
javaMaxHeapSize “2g”
}
- dexOptions{
性能比较
经过测试,当前版本(2016/03/15)的 Jack 编译器比起 Javac+dx 在编译时间,编译出的 apk 体积,编译出的 apk 的性能上暂时并没有优势。
但是,可以期待 Google 将在 Jack 编译器上做大量的智力投资,Jack 的未来是光明的。
下图是 guardsquare 公司对 Javac+dx 和 Jack 做的对比测试
对于不 proguard 的 clean build,javac/dx 耗时 56s, jack 耗时 1 m 48 s;之所以 jack 这么慢是因为它要做大量的 pre-dex。
对于不 proguard 的 clean build,javac/dx 和 jack 编译出来的 app 性能相差无几。
对于共用 proguard 配置文件情况,javac/dx 和jack 编译出来的 app 体积也差不多。
我个人测试的编译速度 / apk 体积等对比也大致如此,在此不再赘述.
结语
虽然 Jack 编译器的现状并不出彩,但是它终究有一天会成为 Android app 的官方推荐编译器。
期待 Google Android team 加倍努力,让这一天早日到来。
参考文献
- https://www.guardsquare.com/blog/the_upcoming_jack_and_jill_compilers_in_android
- http://source.android.com/devices/tech/dalvik/dex-format.html
- http://tools.android.com/tech-docs/jackandjill
- https://developer.android.com/intl/zh-cn/tools/building/multidex.html
- https://www.guardsquare.com/blog/DroidconLondon2015
还没有评论,来说两句吧...