作为一名Android开发,我们总会面对下面这个按钮,对于一些大型项目,或者对于在编译期间做了很多task的工程都会花去相当多的时间。就我而言,经常跑一个debug的包需要3分钟左右,当跑release的包需要10分钟左右,如果是一些性能差的电脑,这个时间会消耗的更多。今天文章的主题有两个部分,一个是Android的编译都做了哪些事,还有一个是如何提高我们的编译是速度以及相关原理。
今天的主题将围绕下面这个按钮开始讲述。
大家在刚学计算机基础这门课程的时候就知道,“计算机只认识0和1,我们写的编译程序经编译器翻译成由0和1构成的二进制格式才能由计算机执行”。所以,我们写的java语言或者kotlin语言最终都会转化成0,1的二进制数据。在这里,这些二进制数据有一个名字—字节码(各种不同平台的虚拟机与所有平台都统一使用的程序存储格式)。在java虚拟机中,使用java编译器可以把java代码编译为存储字节码的Class文件。Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中。对于具体的词法分析、语法分析以及底层编译原理就不在这里细讲了。
具体看下下面的图。
你可以把编译简单理解为,将高级语言转化为机器或者虚拟机所能识别的低级语言的过程。对于 Android 来说,这个过程就是把 Java 或者 Kotlin 转变为 Android 虚拟机运行的Dalvik 字节码(DEX文件)的过程。
javac Sample.java // 生成Sample.class,也就是Java字节码
// javac -source 1.8 -target 1.8 Sample.java 也可以通过指定版本与dex对应上
javap -v Sample // 查看Sample类的Java字节码
//通过Java字节码,生成Dalvik字节码
dx --dex --output=Sample.dex Sample.class
dexdump -d Sample.dex // 查看Sample.dex的Dalvik的字节码
//javac .class
public void test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String 哈哈哈哈
5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
Dalvik字节码
平台支持区别
Dalvik:Android 4.4及其以下系统使用。
ART:Android 4.4以上系统使用。
工作原理区别
我们经常点击构建按钮,就会看到Android studio下方开始不停的执行task,最终会生成一个APK并打开我们设置的launch Activity
。以官方的话来说,
Android 编译系统会编译应用资源和源代码,然后将它们打包成可供您测试、部署、签署和分发的 APK。我们具体可以看下整个构建流程。
总共可以分为以下4部分
在上文我们了解到我们的java和kotlin代码会被Android虚拟机转化成dex文件,那我们的资源文件以及xml又是如何打包的呢。我们来了解下 AAPT2
AAPT2(Android 资源打包工具)是一种构建工具,Android Studio 和 Android Gradle 插件使用它来编译和打包应用的资源。AAPT2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格式。
AAPT2 支持通过启用增量编译实现更快的资源编译。这是通过将资源处理拆分为两个步骤来实现的:
这里我们来讲讲增量编译相关优化方案
增量编译–对用户源程序局部修改后进行的重新编译的工作只限于修改的部分及与之相关部分的内容。相关部分的确定由编译系统确定,对用户是透明的。增量编译对软件开发,尤其是在调试期,可以大大缩短编译时间, 提高编译效率,这也是增量编译的优势所在。
我们在上文了解了整个apk的构建流程,每次正常打apk都需要完整的链路走过一次,假设我们只改动了部分代码,能不能只编译我们改动的部分,其余的部分利用缓存机制,来帮助我们快速构建apk呢。
我们来看下目前市面上已有的编译方案。
Instant Run的作用是使得开发过程中的改动可以不用完整编译并重新安装app就能应用,也就是更快看到改动的实际效果,节省时间。实现的原理是通过修改原先的构建过程在初始编译中实现插桩,在后面的改动中只编译改动的部分,并把产物推送到设备上,并通过植入app中的runtime加载新的变动。
因为 instant-run
存在一些弊端,在 Android Studio 3.5 及更高版本中,Apply Changes
可让您将代码和资源更改推送给正在运行的应用,而无需重启应用(在某些情况下,甚至无需重启当前的 Activity)。但是Apply Changes
使用搭载 Android 8.0(API 级别 26)或更高版本的设备上支持。
在 Android Studio 3.5 及更高版本中,Apply Changes 可让您将代码和资源更改推送给正在运行的应用,而无需重启应用(在某些情况下,甚至无需重启当前的 Activity)。
Apply Changes 使用搭载 Android 8.0(API 级别 26)或更高版本的设备上支持的 Android JVMTI 实现中的功能。对于UI的改动,我们能通过apply changes快速在手机中查看我们修改的地方。
同时,也存在一定限制:
某些代码和资源更改必须在重启应用之后才能应用,其中包括以下更改:
整体设计需要考虑两个点
我们看下Apply Changes
整体框架图
在后台进程中利用D8的DEX文件分析功能来检查Android Studio部署到设备上的每个.dex文件的内容。在.dex文件中的各个类上计算基于校验和的指纹,并将结果临时存储在主机工作站上的缓存数据库中。通过将新编译的指纹与以前的编译的指纹进行比较,“应用更改”能够在短时间内有效地提取更改的类。
“应用更改”需要计算安装的APK与最近构建的APK之间不同的文件,而不必从设备中提取所有内容。这次,它仅获取压缩文件的中央目录,并保守估计相应APK之间的差异。通过仅传输已更改的部分,Android Studio传输的数据比完整的APK上传要少得多。在大多数使用情况下,总有效负载减少到几个KiB,而不是几个MiB。
BUCK建立了一套完善的依赖规则以及细化的缓存系统来缩减编译时间,其增量构建的原理,实际是以工程目录为单位进行增量构建,发生变更时候,变更的工程,以及该工程作为父节点或祖先节点的工程,均需要重新构建。无论是 Buck 的exopackage
,还是代码的增量编译,Buck 都更加高效。Buck还有分包支持,ReDex 支持等功能。
但是BUCK的整体接入成本还是比较高的。要去掉原有的gradle方案
多任务并发,多级缓存,增量范围最小化,懒加载,基于长链接无安装式运行期动态替换,基线对齐触发机制,可调试。详细细节可以参考 Freeline的官方文档。
更换编译机器。装备升级,CPU升级效果杠杠的。
升级 Gradle 和 SDK Build Tools。在新版本中,Android致力于优化Gradle的构建。
采用上述增量编译方案的某一种。
对于debug测试包,我们关心apk构建的速度以便我们能快速开发。而对于需要发布的release包,我们更关心包的大小以及性能的稳定。
接入proGuard后,我们的app构建流程将变化如下。
d8 是一种命令行工具,Android Studio 和 Android Gradle 插件使用该工具来将项目的 Java 字节码编译为在 Android 设备上运行的 DEX 字节码,该工具支持您在应用的代码中使用 Java 8 语言功能。
当您使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件不再使用 ProGuard 执行编译时代码优化,而是与 R8 编译器协同工作。
在编译时做了以下事情
R8 的最终目的 加快编译速度,更强大的代码优化。
ReDex是Facebook开源的Android字节码(dex)优化器,它直接输入的对象是 Dex,而不是“.class”文件。ReDex拥有目前更强大的编译速度以及代码优化。具体配置可以参考 Redex官方。能有效的降低包提及,优化了android原有的打包策略。
对于如何提高编译速度, google在每个版本升级中都进行了优化,同时,行业内也开源了自身的好的编译方案,对于我们日常的业务开发中,可以根据自身的业务需求来选择方案。
对于我们日常的debug开发,为了能快速看到我们相关功能的具体实现。增量编译的速度成为了关键,在编译过程中,我们看到了一些代码插桩的技术。将在之后给各位详细介绍Android中如何进行代码插桩的方案和一些用例。
https://time.geekbang.org/column/article/82468
https://source.android.com/devices/tech/dalvik/dalvik-bytecode
https://developer.android.com/studio/build?hl=zh-cn
https://developer.android.com/studio/command-line/aapt2?hl=zh-cn