一、前言
我们项目的APP经过不同团队的迭代开发,以及后续因为业务调整,将另一款APP也融入到主APP中(由于迭代时间以及历史原因,两个APP采用最粗暴的代码合并方式融合在一起),使得包体积由原来的100多M,激增到306M,远远超过了苹果规定的200M的下载限制,因此不得不开始考虑进行体积优化了(本期只讲方案,包体检测以及优化方案原理后期逐步更新)。
二、包体积优化的必要性
1. App Store的限制
对于使用蜂窝数据网络下载应用的用户,App Store有一个初始下载限制,通常为200MB。这意味着应用的初始下载大小不能超过这个限制,超过限制的内容需要用户在连接 Wi-Fi 后才能下载(iOS13之前,iOS13开始可以设置),这大大影响了用户的下载意愿。
2. 用户体验
较小的应用包体积可以提供更快的下载、安装和更新速度,从而提升用户体验。用户更倾向于选择下载和保留体积较小的应用,以节省设备存储空间。
3. 下载和安装时间
大型应用需要更长的下载和安装时间,可能会让用户感到不耐烦,甚至放弃下载。小巧的应用能够更快地进入用户设备,降低了用户等待的时间。
4. 设备存储空间
用户设备的存储空间是有限的资源。应用体积较大可能会占用大量存储空间,导致用户不得不删除其他应用或文件来腾出空间,或者购买更大容量的设备。
5. 移动数据消耗
下载和更新大型应用可能会消耗用户的移动数据流量,对用户来说可能是额外的成本。小型应用可以减少数据使用。
6. 市场竞争
在App Store中,用户往往会比较不同应用的大小。较小的应用可能在用户眼中更具吸引力,有助于应用在激烈的市场竞争中脱颖而出。
7. 更新管理:
频繁的大型更新可能会打扰用户并增加用户流失的风险。
包体积查看:用户侧看到的大小
开发人员从appstoreconnect上看到的是两个体积,下载大小和安装大小。
这里的大小分为两个口径:
下载大小 和 安装大小。
根据网页上的说明:
安装大小 是这个 app 安装后,会占用的磁盘大小;
下载大小 是 app 经过压缩后的大小。
根据经验,用户在 app store 上看到的大小,就是 itunes connect 后台中显示的 安装大小。
而令开发者在意的,“超过 200 MB 的 app 必须连接至无线局域网才能下载”的规则中的 200 MB,指的其实是 下载大小。
三、包体组成部分
通过上图,可以清晰看到可优化的点,接下来逐步优化就好了。
3.1 资源优化
3.1.1 废弃资源删除
由于历史原因,存在很多僵尸类以及资源文件,逐步删除就行了,删除的时候可以利用一些检测工具批量删除,这里不做赘述。无用的类删除后,对应的资源也可以一起删除。
3.1.2 资源优化
资源优化无非就是那几种,我们采用的是压缩文件和本地资源网络化,其实也可以通过更改图片格式的方式,改成webp,但是需要代码支持,由于各方成本比较高,暂时没有尝试。
利用工具将图片进行无损压缩,压缩的过程发现很多很大的图,有的一张甚至几M,这个就很浪费资源了。经过几轮压缩,整体资源减少了大几M,接下来就是资源网络化了(网络化之前的压缩为了更快的下载资源)。
这部分也分为两块,可以利用自己的图片服务器进行上传,也可以利用苹果官方提供的On-Demand Resource方案,我们项目中两种方案都用到了,下面就介绍一下两种方案的不同点。
3.1.2.1 On-Demand Resource
在介绍之前,先说两点
1、ODR中的资源是在APP打包的时候确定的,不进行版本更新就无法更新这些资源。
2、ODR的资源是存放在苹果Server的,我们不需要再用自己的服务器。
如何开启:在build settings中搜索assets,将Enable On Demand Resources设置为YES,然后再Resource Tags下面添加对应的分组就好了。
On-Demand Resource的三种标签:
- Initial install tags.此种标签的资源,会随着App从App Store下载而下载,但是会影响App的ipa大小,也就是说此种资源会包含在ipa内。
- Prefetch tag order.此种标签会在App下载后,开始下载相应的资源,下载是存在顺序的,后面会说明。此种资源并不会影响ipa的大小,也就是说此种资源并不包含在ipa内。
- Dowloaded only on demand. 此种标签下的资源,会在必要的时候,主动触发下载,这是我们开发者自己控制下载时机的。
我们项目中采用的是第二和第三种。先说下原因:上面介绍了,我们有个第三方的大项目集成到主项目中了,而且打开的入口很集中,因此,该项目的所有资源都可以集中统一处理。我们在项目启动的时候,会加载这个项目中的部分资源(必要的),在点击跳转的时候会判断是否下载完成,如果没有,则继续下载直到下载完成为止。由于我们每个业务模块的调用相对集中且单一,因此在入口处处理就可以了,如果很分散,不建议用这种方式处理。
这种方案对于我们来说,只适合去做一些统一模块的处理,把整个模块的资源包统一下载下来,一旦失败就需要在业务调用的时候重新下,代码逻辑比较重,无法做到无痕处理,对于老项目来说一些分散的业务模块比较难维护,因此我们只用在了乐屋部分的业务处理。其他模块则采用第二种方式。
3.1.2.2 远程资源服务器
将本地图片资源放到远程服务器,按需下载,优点是可以根据业务需要按需加载,利用sd等第三方的图片处理方案做几级缓存,这样既节省了流量资源也节省了内存资源(本质就是加载网络图片的那套逻辑)。缺点就是需要维护网络资源,以及做版本管理,远程冗余的资源要定时清除。
这部分内容我们将图片按大小以及模块做了个分类,10k以上的资源全部迁移到远程,以下的仍然本地存储,这部分优化掉接近20M的空间。
3.1.3 符号剥离
strip在iOS中的作用是 剥掉目标文件中一些符号信息和调试信息,使文件变小。
strip:移除指定符号。在Xcode中默认strip是在Archive的时候才会生效,移除对应符号
值得注意的是,并不是所有文件的符号都是可以剥离的:
动态库 不能strip全局符号、因为全局符号要作为导出符号。
App中 间接符号表中的符号不能strip;那么App中 本地符号、全局符号都可以strip。
静态库 = .o文件合集,存在重定位符号表,这个表中的数据也是不能strip的。所以 .o文件中能strip的是调试符号。
3.1.3.1 在xcode中配置
在Build Setting中设置Deployment Postprocessing,在编译阶段就会运行strip
设置Strip Debug Symbols During Copy
当应用在编译阶段copy了某些二进制文件时,打开该选项会脱掉该二进制的调试符号。但是不会脱去链接的最终产物(可执行文件\动态库)的符号信息。要脱去链接的产物(App的可执行文件)的符号信息。
设置Strip Linked Product
如果没有打开Deployment Postprocessing,则会在Archive处理链接的最终产物(可执行文件)的符号信息。否则,在链接完成之后就会处理符号信息
设置Strip Style(符号剥离级别),它会在生成可执行文件后进行优化,相当于对Mach-O文件进行修改
All Symbols:除了间接符号表中使用的符号,其他符号都移除(上架App使用)
Non-Global Symbols:除了全局符号都可以移除 (动态库使用)
Debugging Symbols:移除调试符号(静态库使用)
因为 App在链接静态库的时候,.o文件的合集会把.o中的所有符号、包括重定位的符号,都放到App的符号表中。因此可能变成了全局符号、本地符号、导出符号。那么根据strip的原理、在静态库中所有的符号都可以被剥离. 而动态库中所有的符号都被放到App的间接符号表中、那么再去strip的时候、动态库中的死代码将不能被剥离。
3.1.3.2 终端执行
还有一种方式,是直接找到framework的二进制文件路径,执行 xcrun strip -xS AAA -o AAA_strip,AAA是原始文件名称,-o后面是导出后的名称,但是这种方式会导致部分文件签名问题,需要重新签名。用这种方式,我们包体积减了20多M。
四、总结
通过以上方式,安装包由306M减少到了200M,下载包在80M左右,效果还是很明显的。 在实际项目瘦身过程中,上面列举的方式不一定都适用。比如图片放在远程,需要搭建自己的远程库,或者利用阿里云,但是并不是所有的图片都适合。例如当项目具有动态化,许多类和方法都不会在代码中直接产生引用,而可能是通过接口方式下发数据来使用,如果没有配置表会容易产生误删。有的资源是在xib中或者Swift中,有的工具检测不到容易误删,所以在删除资源的时候一定要慎之又慎。。。。安全第一。。。。
工具汇总
-
性能检测:WBBlades
-
无用图片检测:LSUnusedResources
-
图片压缩:ImageOptim、tinypng、Webp
-
查看Mach-O内容:MachOView
-
静态检测代码:LinkMap结合Mach-O 或 Appcode静态检测