优化Android编译速度:微信、Google、Facebook等大厂的经验分享

2025-01-04
来源:网络整理

作为一名工程师,我们每天都会经历无数的编译。对于小项目来说,半分钟或者1、2分钟就可以编译完成,但是对于大项目来说,每次编译可能要花一杯咖啡的时间。如果我告诉你具体的数字,也许你会更好理解。我在微信团队的时候,完全编译包需要5分钟,编译包需要15分钟多。

如果每次编译能减少1分钟,整个微信团队就可以节省1200分钟(团队40人×每天30次编译×1分钟)。因此,优化编译速度对于提高整个团队的开发效率非常重要。

那么我们应该如何优化编译速度呢?微信、微信等国内外各大企业都做出了哪些努力?除了编译速度之外,关于编译你还需要了解什么?

编译

虽然我们每天都在编译,但是编译到底是什么?

你可以简单地将编译理解为将高级语言转换为机器或虚拟机可以识别的低级语言的过程。对于Java来说,这个过程就是将Java转换成虚拟机可以运行的字节码的过程。

整个编译过程涉及词法分析、语法分析、语义检查、代码优化等步骤。对底层编译原理感兴趣的同学,可以挑战一下编译原理的三部经典巨著:龙书、虎书、鲸书。

但今天我们的重点不是底层的编译原理,而是一起讨论编译中需要解决哪些问题,目前为止遇到了哪些挑战,国内外各大厂商都提供了哪些解决方案。

编译基础知识

无论是微信的编译优化还是项目,都涉及到很多编译相关的知识,所以我对编译做了很多研究,有丰富的经验。编译构建过程主要包括代码、资源三部分。整个过程可以参考官方文档的构建流程图。

它是官方的编译工具,也是互联网上的一个开源项目。从更新日志可以看到,这个项目目前更新非常频繁,基本上每两个月就会有一个新版本。对于我来说,最痛苦的就是写作,主要是因为这方面没有完整的文档,所以一般只能依靠看源码或者断点调试。最近,我的公司正在计划使用一个渠道打包工具,对项目打包和构建流程有了深刻的了解。

但编译这么重要,而且每个公司的情况都不一样,所以必须强行打造自己的一套“轮子”。已经开源的项目包括 Buck 和 .

为什么我们要自己“发明轮子”?主要有以下几个原因:

“程序员最讨厌写文档,也有人不写文档。”所以他们的文档都比较小,如果想做二次定制开发会很痛苦。如果要切换编译工具为Buck,需要下很大的决心,还需要考虑与其他上下游项目的协作。当然,即使我们不直接使用它们,它们内部的优化思想也值得学习和参考。

、巴克,都是为了更快的编译速度和更强大的代码优化。我们来看看他们都做了哪些努力。

编译速度

回想我们的开发生涯,在编译上浪费了多少时间和生命。正如我之前所说,编译速度对于团队效率非常重要。

关于编译速度,我们最关心的可能是编译包的速度,尤其是增量编译的速度()。我们希望能够实现更快的调试。如下图所示,我们每一个代码验证都要经过两个步骤:编译和安装。

这里,我们从两个维度来看编译速度:编译时间和安装时间。

对于增量编译,先说一下官方的解决方案Run。 2.3之前,它使用实现。 2.3之后,使用5.0中添加的APK机制。

如下图所示,资源和资源都放在Base APK中。 Base APK中的代码只是Run框架,应用程序自己的代码都在APK中。

共有三种运行模式。如果是热插拔或者温插拔,我们不需要重新安装新的APK。区别在于是否重启。对于冷插拔,我们需要通过adb - -r -t 重新安装更改后的APK,并且还需要重新启动应用程序。

虽然无论哪种模式,我们都不需要重新安装Base APK。这使得Run看起来很不错,但是在大型项目中,它的性能仍然很差,主要是因为:

你也可以看看这个:“full if aa”。假设修改后的类中包含一个“”变量,那也是尴尬了。这个修改以及它所依赖的模块需要是完整的。这是为什么呢?因为常量池会直接将值编译到其他类中,所以我们不知道哪些类可能会使用这个常量。

当我询问工作人员时,他们给出的解决方案是:

// 原来的常量定义: public static final int MAGIC = 23 // 将常量定义替换成方法: public static int magic() { return 23; }

这对于大型项目来说肯定是不可行的。正如我在 中所写的,无论我们是否真的更改为这个常量,都将是不假思索的彻底更改,这肯定是错误的。事实上,我们可以通过对比这段代码修改,看看某个常量的值是否真的被改变了。

但是用过阿里或者蘑菇街的快速编译的同学可能会有疑问,为什么他们的解决方案没有遇到常量的问题呢?

事实上,在大多数情况下,他们的解决方案比 Run 更快,因为牺牲了正确性。也就是说,为了追求更快的速度,他们直接忽略了改变常量时可能导致错误的编译产物。作为官方的解决方案,Run的首要任务是保证100%的准确率。

当然,人们也发现了Run的各种问题。 3.5之后,8.0之后的设备将使用新的解决方案“ ”来替代Run。我还没有找到有关此解决方案的更多信息,但我认为 APK 机制应该被放弃。

我心中一直有一个理想的编译方案。该方案安装的Base APK仍然只是一个 APK,真正的业务代码放在.dex中。其架构图如下。

我还有一些优化编译速度的建议:

相比之下,或许最流行的中热二级编译功能会更有吸引力。

当然,在最近的版本中,还做了很多其他的优化,比如使用AAPT而不是AAPT来编译资源。实现了资源的增量编译,将资源的编译分为两步:链接。前者资源文件以二进制形式编译成平面格式,后者则将所有文件合并然后打包。

此外,还推出了d8和R8。这里提供一些测试数据,如下图所示。

那么d8和R8是什么?除了优化编译速度之外,它们还有哪些功能呢?您可以参考以下介绍:D8和R8

代码优化

对于包编译来说,我们更关心的是速度。但对于包来说,代码优化更重要,因为我们会更加关注应用程序的性能。

接下来我会讲一下d8、R8以及我们可能会用到的这四种代码优化工具。

在微信包12分钟的编译过程中,仅需要8分钟。虽然它确实很慢,但基本上每个项目都会用到它。加入后,应用构建流程如下:

主要有混淆、裁剪、优化三大功能。其整个处理流程如下:

其中包括内联、修饰符、合并类和方法等30多项优化,详细介绍和使用可以参考官方文档。

D8

d8在3.0中引入,并在3.1中正式成为默认工具。它的功能是编译“.”。文件转换为Dex文件,取代了之前的dx工具。

除了更快的编译之外,d8 还进行了一项优化,可以减少生成的 Dex 大小。根据测试结果,大约会有3%到5%的优化。

R8

R8是在3.1中推出的,它的野心甚至更高。它的目标是取代d8。我们可以直接用R8转“.”文件写入 Dex。

同时,R8还支持混淆、裁剪、优化三大功能。由于R8还处于实验阶段,所以网上的介绍资料并不多。您可以参考以下材料:

与R8相比:

R8:其中的一个。

Jake大师的博客最近有很多R8相关的文章:。

R8的最终目标和d8一样,一是加速编译,二是更强大的代码优化。

如果R8是你将来想要替换的工具,它已经在内部使用了。很多内部项目已经转用,不再使用。不同的是,它的直接输入对象是Dex,而不是“.”。文件,即直接针对最终产品进行优化,所见即所得。

在之前的文章中,我不止一次提到过这个项目,因为里面的功能实在是太强大了。具体可以参考《包体积优化(上):如何减小安装包大小?》专栏的上一篇文章。 》。

另外,Type、去除代码等方法也是非常好的功能。它们对于包大小和应用程序运行速度都有帮助,因此我也鼓励您研究和练习它们的用法和效果。

不过文档已经几千年没有更新了,而且包含了一些内部定制的逻辑,所以使用起来确实很不方便。目前我主要是直接研究它的源码,参考它的原理,然后直接单独实现。

事实上,Buck中其实有很多有用的东西,但是文档中仍然没有提及,所以你仍然需要“阅读代码”。

持续交付

、巴克,它们都代表狭义的编译。我觉得广义上的编译应该包括打包构建、Code、代码工程管理、代码扫描等流程,也就是最近业界经常提到的持续集成。

目前最常用的持续集成工具有CI、CI、CI等,有的还提供自己的持续集成服务。每个大公司都有自己的持续集成解决方案,比如腾讯的RDM、阿里巴巴的摩天轮、大众点评的MCI等。

我简单说一下我对持续集成的一些经验和看法:

持续集成涉及到的流程有很多,需要结合团队目前的情况进行结合。如果只是盲目的添加进程,有时可能会适得其反。

总结

8.0中引入了库实现类和方法的重新整理,也首次引入了Buck。 、d8和R8实际上是互补的。可见我们也在吸收社会的知识,但同时我们也会从新技术的发展中寻求思路。

在写今天的内容时,我还有另外一个体会。为了解决编译速度的问题,我花费了很大的力气,但是结果却并不理想。我想说,如果我们敢于突破系统的限制,或许就能彻底解决这个问题,就像我们上面可以实现完美的二级编译一样。其实生活和工作也是如此。我们常常会陷入局部最优解的困境,或者进入“思维圈”。这个时候,如果我们能够跳出路径依赖,从更高的维度重新思考和审视全局,我们能够获得的经验可能会完全不同。

分享