在做安装包大小优化前,我们应该首先搞清楚,用户在 app store 上看到的包大小,究竟是什么?
如果我们衡量安装包大小的口径,和用户看到的大小不一致,那么做优化时的优先级和ROI衡量就可能跑偏,甚至出现优化效果为负的悲惨结局。
首先抛出结论:用户在 app store 上看到的包大小,是:
- .app 文件
- 的二进制部分被加壳后
- 再经过 app slicing
的大小。
如何查看
在某个版本的 app 上线之前,开发者应该如何知晓它在用户眼中的大小呢?
苹果的 itunes connect 后台为开发者提供了查看安装包大小的功能。
在 itunes connect 后台,开发者可以看到当前版本针对不同机型的大小。
这里的大小分为两个口径:
Download Size 和 Install Size。
根据网页上的说明:
Install Size 是这个 app 安装后,会占用的磁盘大小;
Download Size 是 app 经过压缩后的大小。
根据经验,用户在 app store 上看到的大小,就是 itunes connect 后台中显示的 Install Size。
而令开发者在意的,“超过 150 MB 的 app 必须连接至无线局域网才能下载”的规则中的 150 MB,指的其实是 Download Size。
几个口径
Download Size 和 Install Size 是如何计算出来的,我们等下再说。
抛开 itunes connect 和 app store,平时我们在开发打包一个 app 时,经常会接触到这样几个安装包概念:
- ipa
- app
ipa 可能是我们最熟悉的“安装包”的格式。通过 Xcode 的 archive 方式,最终打出的安装包的格式就是 ipa。
实际上,ipa 就是一个 zip 压缩包。我们可以用 unzip 的方式来解压一个 ipa 文件。
这样,我们会得到一个后缀名为.app的文件。
甚至可以想进入一个文件夹一样进入到这个.app中,窥探二进制、各个资源分别占据了多少大小。
这里我们知道了,.app 文件和 .ipa 文件的关系:
ipa 文件是 app 文件 zip 压缩后的产物。
用户看到的大小
那么用户看到的 Install Size,究竟是 .app 文件的大小,还是 .ipa 文件的大小呢?
答案:都不是。但是 .app 文件的大小与 Install Size 的口径更为接近。
加壳
苹果在 iOS 9 推出了能减小安装包的 app thining 功能。我们先不考虑这个功能,看看 iOS 9 以前 Install Size 与 .app 文件的关系。
要获取 app store 上的安装包,其实没有正规的做法。有一个比较trick的技巧可以获得:
https://www.jianshu.com/p/ce018473fad0
通过这个方式,我们可以下载到一个 .ipa 文件。
这个 ipa 文件经解压后得到的 app 文件,其大小与 iOS 9 以下设备在 app store 上看到的大小是吻合的。也与 itunes connect 中,开发者看到的 Install Size 是吻合的。
但是,它与开发者提交到 itunes connect 前的 app 文件大小,是有一定差距的。
比如,我们提交到 app store 上的 ipa 解压后的大小为 297.1MB
从 app store 上下载的 ipa 解压后大小为 301.9MB
其中这 3.8 MB 的大小差异来自哪里呢?
来自加壳。
我们对比了这两个 app 包中的各个文件大小,发现各资源文件的大小完全一致,只有二进制文件的大小发生了改变。
使用 otool 命令
otool -l 可执行文件路径 | grep crypt
我们可以验证,app store 中下载的包经过了加壳,而提交 itunes connect 前的包没有。
使用
otool -l
命令可以输出 Mach-O 文件加载的 load command。经对比发现,虽然加壳改变了TEXT段的内容,却没有改变TEXT段的大小。这 3.8 MB 的大小差异主要来自 __LINKEDIT 和 LC_CODE_SIGNATURE 这两个段,这两个段都与动态链接器有关。
因此,我们可以得到结论:iOS 9 之前,用户看到的 Install Size 的大小,是 .app 文件经过加壳的大小,粗略的可以认为就是 .app 文件的大小。
app slicing
上述的分析针对于 iOS 9 以下设备看到的情况。
如果考虑苹果在 iOS 9 上推出的 app thining 功能,安装包大小会有什么影响呢?
我们先了解一下,如果没有 app thining,一个安装包中会包含哪些内容。
对二进制来说,由于一个安装包需要同时支持 iPhone 5 等 32 位设备和 iPhone 5s 以上的 64 位设备,所以二进制中需要包含 armv7 和 arm64 两个架构的 Mach-O 文件。
对于放在 asset catalog 中的资源来说,一般来说,开发者为了更好的适配 iPhone 6 等 2x 屏幕的设备和 iPhone 6 plus 等 3x 屏幕的设备,每一个图片资源会引入 2x 和 3x 两个版本。
对于其他图片/资源来说,它们也会被收入到安装包中。
这里,我们可以明显发现,如果一个设备下载安装了安装包中的全部内容,有两个明显浪费的地方:
- 有一份它不需要的 Mach-O
- 有一份它不需要的资源图
苹果在 iOS 9 推出的 app slicing,就帮助开发者将这两个浪费的地方干掉了。
对于资源图,官方文档中有说明:
A thinned .ipa is a compressed app bundle that contains only the resources needed to run the app on a specific device.
https://developer.apple.com/library/archive/qa/qa1795/_index.html
对于二进制,我们没有在官方文档中找到说明。但 WWDC App Thinning in Xcode 一篇中这样介绍了 app slicing 功能:
这张图中体现了,无论是二进制还是 asset catalog 中的资源,app slicing 都只保留了当前设备所需的部分。
而在导出 ipa 时,在配置 plist 文件中加入 <thin-for-all-variants> 选项,则可以导出针对不同机型的 ipa 文件。经实验,这些 ipa 文件解压后得到的 app 文件大小,与 itunes connect 后台各个设备对应的 Install Size 是吻合的。
优化建议
正如文章开头的结论:
用户在 app store 上看到的包大小,是:
- .app 文件
- 的二进制部分被加壳后
- 再经过 app slicing
的大小。
得知了用户看到的包大小究竟是什么含义,我们可以避开一些包大小优化的坑。
比如,由于 app slicing 的存在,图标应该尽可能用 asset catalog 管理起来。所以试图用 webp 等格式来替代 asset catalog,可能是负向收益。
(这个思路经过实验,结论是,在我们的业务场景下,如果用 webp 替代 asset catalog,对于 2x 设备的 Download Size 是负向收益,因为 2x 设备不得不使用 3x 的图片,并且 webp 图的压缩率比 asset catalog 低很多)
再比如,不要幻想抛弃 armv7 架构的设备,可以减小包大小。苹果已经帮开发者做过这一步了。