Android 7.0增加jack跟jill编译器(转)

Android 新一代编译 toolchain Jack & Jill 简介

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 的缘起,工作方式和原理。
JackJava 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 Javabyte 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)

下面是用流程图表示的旧编译过程

dx

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 预处理过程

jill

下面是用流程图表示的 Jack 编译过程
jack

各种依赖库仍然以 jar/aar 的形式存在
辅助工具 Jill 将根据依赖库中的 .class
生成 Jayce 格式的 IL,并调用 Jack 做 pre-dex 并生成 .jack
,此过程只在编译 app 时发生一次
Jackjava 源代码也编译为 .jack
,然后将多个 .jack
转化为单一的 .dex
; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex

pre-dex 的详细解释可以参阅此链接 new-build-system

Improving Build Server performance.The Gradle based build system has a strong focus on incremental builds. One way it is doing this in doing pre-dexing on the dependencies of each modules, so that each gets turned into its own dex file (ie converting its Java bytecode into Android bytecode). This allows the dex task to do less work and to only re-dex what changed and merge all the dex files.

.Jack中间文件
.Jack
的具体格式如下图所示

.jack

可见里面包含了 Jayce 格式的 IL ,pre-dex,原始 aar 中的资源文件,以及 Jack 会用到的一些 meta 信息
下图简单比较了 java 代码转化的 .class
, Jayce IL 和 dex 的内容异同
compare

简单比较下三种 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 文档是这么介绍的

This server brings an intrinsic speedup, because it avoids launching a new host JRE JVM, loading Jack code, initializing Jack and warming up the JIT at each compilation. It also provides very good compilation times during small compilations (e.g. in incremental mode).The server is also a short-term solution to control the number of parallel Jack compilations, and so to avoid overloading your computer (memory or disk issue), because it limits the number of parallel compilations.

支持 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 足够复杂之后,在打包时常常会遇到这种错误提示

Unable to execute dex: method ID not in [0, 0xffff]: 65536

为什么方法数目不能超过 65k 呢?有人说是 dexopt 的问题,有人说是 dex 格式的限制,下面我们看看这个 log 到底是哪里吐出来的,然后分析下具体原因。
dex 格式的限制?

首先我们看一下 dex 的结构定义

//Direct-mapped "header_item" struct.struct DexHeader { ... u4 methodIdsSize; ...};//These match the definitions in the VM specification.typedef uint32_t u4;

可见 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 是哪里吐出来的?

//MemberIdsSection.javaif (items().size() > DexFormat.MAX_MEMBER_IDX + 1) { throw new DexIndexOverflowException(getTooManyMembersMessage());}/Maximum addressable field or method index.The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or meth@CCCC./ public static final int MAX_MEMBER_IDX = 0xFFFF;

通过查阅 dalvik-bytecode 可知,@CCCC 的范围必须在 0~65535 之间。
所以归根结底,65k 问题是因为 dalvik bytecode 中的指令格式使用了 16 位来放 @CCCC 导致的;所以,不仅 Method 数目不能超过 65k, Field 和 Class 数目也不能超过 65k。
为什么 jack 没有 65k 问题
前文已经很清楚地解释了 65k 问题的由来,可见只要 dalvik bytecode 指令格式不升级,65k 问题是逃不掉的。
Jack 官网对 65k 问题是这么说的:

Multidex supportSince dex files are limited to 65K methods, apps with over 65K methods must be split into multiple dex files. (See ‘Building Apps with Over 65K Methods’ for more information about multidex.)Jack offers native and legacy multidex support.

所以,Jack 和旧工具链对 multidex 的支持方式是相同的
Jack 编译出来的 app 执行时也和以前一样
若是 dalvik 虚拟机,它只支持读取一个 classes.dex。而 multidex 解决方案会读取多个 .dex
,帮我们做 dex 数组合并
若是 art 虚拟机,它会扫描 classes.dex, classes1.dex…classesN.dex,调用 dex2oat 转化为单一的 oat

Jack 是怎么支持 Java 8 的?
以 lambda 表达式为例

Interface lambda = i -> i + 1;

会被转化为 anonymous classes

Interface lambda = new Interface() { public int m(int i) { return i + 1; }};

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.build:gradle:2.1.0-alpha2'}

使用以下版本的 sdk 和 build-tool

compileSdkVersion 'android-N'buildToolsVersion '24.0.0 rc1'

在 defaultConfig 中指定用 Jack

defaultConfig { jackOptions { enabled true }}

使用 gradle 2.10 以上

distributionUrl=http://mirrors.taobao.net/mirror/gradle/gradle-2.10-bin.zip

使用 Android Studio 2.1 (preview) 或者命令行编译

可能需要提升 javaMaxHeapSize

dexOptions{ javaMaxHeapSize "2g"}

性能比较
经过测试,当前版本(2016/03/15)的 Jack 编译器比起 Javac+dx 在编译时间,编译出的 apk 体积,编译出的 apk 的性能上暂时并没有优势。
但是,可以期待 Google 将在 Jack 编译器上做大量的智力投资,Jack 的未来是光明的。
下图是 guardsquare 公司对 Javac+dx 和 Jack 做的对比测试

buildtime

对于不 proguard 的 clean build,javac/dx 耗时 56s, jack 耗时 1 m 48 s;之所以 jack 这么慢是因为它要做大量的 pre-dex。
performance

对于不 proguard 的 clean build,javac/dx 和 jack 编译出来的 app 性能相差无几。
size

对于共用 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

**分享到toolchain Jack&Jill

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容