热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android新一代编译toolchainJack&Jill简介

2016年3月10日,Google向外界发布了AndroidN的预览版,并宣布了AndroidN的Roadmap,AndroidN的最终版源代码将于今年8或

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 & Jillinitial 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

  1. javac 将 java 代码编译为 java bytecode, 以.class的形式存在; 以 jar 和 aar 形式存在的依赖库,代码在里面以一堆.class 的形式存在
  2. Proguard 工具读取 Proguard 配置,对.class做 shrinking, obfuscation,输出 Proguard mapping
  3. dx 将多个.class转化为单一的 classes.dex ; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex

新编译流程

新的编译过程只有一个阶段了,它完全抛弃了 javac, ProGuard, jarjar 等工具,一个工具搞定一切

Jack (.java –> .jack –> .dex)

下面是用流程图表示的 Jill 预处理过程

jill

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

jack

  1. 各种依赖库仍然以 jar/aar 的形式存在
  2. 辅助工具 Jill 将根据依赖库中的.class生成 Jayce 格式的 IL,并调用 Jack 做 pre-dex 并生成.jack,此过程只在编译 app 时发生一次
  3. Jack 将 java 源代码也编译为.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 对打包流程的影响

  1. 不再需要独立的 ProGuard。Jack 支持读取旧的 ProGuard 配置,完成 shrinking, obfuscation 的工作
  2. 不再需要独立的 jarjar。Jack 支持读取旧的 jarjar 配置,完成 repackaging 的工作
  3. 没有.class文件了,直接操纵或读取 Java 字节码的各种工具如 JaCoCo/Lint/Mokito/Retrolambda 没有了用武之地。但是仍然可以在 Android Library 上使用这些工具,编译为 aar/jar 后作为 Jill 的输入
  4. annotation processors 如 Dagger, ButterKife 仍可以使用
  5. Scala/Kotlin 等第三方 JVM 语言编写的内容必须先被 Jill 处理,再作为 Jack 的输入

Jack 当前的局限(截止到2016/03/15)

  1. 暂时还不支持 Android Studio 2.0 的 Instant Run 特性
  2. 暂时还不支持 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.java

if (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 support

Since 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 执行时也和以前一样

  1. 若是 dalvik 虚拟机,它只支持读取一个 classes.dex。而 multidex 解决方案会读取多个.dex,帮我们做 dex 数组合并
  2. 若是 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 加倍努力,让这一天早日到来。


推荐阅读
  • 1.在gradle中添加依赖在主项目的build.gradle中添加Dagger2库的依赖dependencies{compilecom.google.dagger:dagger: ... [详细]
  • #前言AndroidArchitectureComponents是谷歌在GoogleIO2017发布的。官方的描述:https:developer.android.google.c ... [详细]
  • 如何在MAC上配置Python和Appium开发环境
    一、需要安装的软件二、下载安装步骤2.1、安装jdk2.1.1JDK下载地址:https:www.oracle.comjavatechnologiesjavase-download ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 智能制造数据综合分析与应用解决方案
    在智能制造领域,生产数据通过先进的采集设备收集,并利用时序数据库或关系型数据库进行高效存储。这些数据经过处理后,通过可视化数据大屏呈现,为生产车间、生产控制中心以及管理层提供实时、精准的信息支持,助力不同应用场景下的决策优化和效率提升。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 在处理历史交易表时,发现存在部分重复交易记录,需进行数据清理。为解决此问题,考虑构建一个临时表,并采用SQL Server ODBC工具进行数据的导入与导出操作,以实现高效去重。此外,建议结合索引优化和批处理技术,进一步提升数据处理效率和系统性能。 ... [详细]
  • 全新发布的自我修复与自我更新的Linux版本,专为云计算环境设计! ... [详细]
  • 深度森林算法解析:特征选择与确定能力分析
    本文深入探讨了深度森林算法在特征选择与确定方面的能力。提出了一种名为EncoderForest(简称eForest)的创新方法,作为首个基于决策树的编码器模型,它在处理高维数据时展现出卓越的性能,为特征选择提供了新的视角和工具。 ... [详细]
  • Go语言中Goroutine与通道机制及其异常处理深入解析
    在Go语言中,Goroutine可视为一种轻量级的并发执行单元,其资源消耗远低于传统线程,初始栈大小仅为2KB,而普通线程则通常需要几MB。此外,Goroutine的调度由Go运行时自动管理,能够高效地支持成千上万个并发任务。本文深入探讨了Goroutine的工作原理及其与通道(channel)的配合使用,特别是在异常处理方面的最佳实践,为开发者提供了一套完整的解决方案,以确保程序的稳定性和可靠性。 ... [详细]
  • 在Python 3环境中,当无法连接互联网时,可以通过下载离线模块包来实现模块的安装。具体步骤包括:首先从PyPI网站下载所需的模块包,然后将其传输到目标环境,并使用`pip install`命令进行本地安装。此方法不仅适用于单个模块,还支持依赖项的批量安装,确保开发环境的完整性和一致性。 ... [详细]
  • SQL 语句的性能优化:你做到了吗?
    在 SQL Server 查询性能优化中,书签查找的作用不容忽视。本文深入探讨了书签查找对查询效率的影响,并提供了实用的优化策略,帮助开发者提升数据库性能。通过具体案例分析,文章展示了如何有效避免不必要的书签查找,从而显著提高查询速度和系统响应时间。 ... [详细]
  • 解决发布版APK构建失败的问题 ... [详细]
  • (转载请注明出处:http:blog.csdn.netbuptgshengod)1.背景      在android源码中我们能看到各种以@开头的字符,他们大多出现在注释中 ... [详细]
  • 本文整理了Java中javax.lang.model.util.Types.getNullType()方法的一些代码示例,展示了Types.getNullT ... [详细]
author-avatar
手机用户随便转转
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有