一.前言
App安装包日益增大原因
随着技术飞速发展,功能不断迭代,APP不断更新和改版,越来越大、越来越全,因此造成了app安装包的不断膨胀。
- 新功能的增加:新的功能需要更多的代码和资源来实现,导致APP体积增大。
- 针对不同设备的优化代码:为了在各种不同分辨率和硬件平台的手机上正常使用,APP需要针对每种设备分别编写优化代码。这种"碎片化"代码会包含很多冗余代码,从而增加软件体积。
- 硬件设备规格的改变:当硬件设备的某些规格改变后,APP也必须做出相应的改变,这通常会导致APP体积增大。例如,当iPhone的屏幕分辨率提高后,APP需要适配新的分辨率,导致界面素材的部分需要占用更大的存储空间。
- 优先适配旗舰机:大部分APP在测试时优先适配旗舰机,这会导致在低端机上使用时可能出现不流畅或闪退等问题,而高端机则不会出现这些问题。
安装包大小首今夕对比
为什么要优化包大小
在精细化运营的角逐中,用户体验、版本更新、内容品质、安装包大小等因素都影响着移动应用厂商的生存状态。特别是安装包体大小,无论是手游还是移动应用,包体变大不仅关系着用户下载转化,也关系着渠道推广成本和难度,在整体大盘用户规模增长的情况下,应用获量的竞争日益激烈,有40.8%的开发者认为投入产出比相对于去年同期呈现下降趋势,51.5%的开发者认为,与去年同期相比应用产品的获客成本单价有所上升,这是用户获取成本的增加与付费能力的限制。(来自网络)
对比举例
为什么微信小程序相比app 有更好的转化和推广能力呢,其实有很大原因也是因为用户能快速使用安装
微信小程序与APP相比有以下优势:
- 无需下载和安装:微信小程序可以直接在微信中使用,无需下载和安装,这样可以节省用户的存储空间和下载时间。
- 快速加载:微信小程序采用轻量级的技术架构,加载速度快,用户可以快速打开使用。
- 无需更新:微信小程序可以在后台自动更新,用户无需手动更新应用程序。
- 便于分享:微信小程序可以通过微信分享给好友、群组或朋友圈,方便用户分享应用程序。
- 轻量级:微信小程序相对于App来说,占用的存储空间更小,对手机性能的消耗也更少。
- 跨平台:微信小程序可以同时运行在ios和Android平台上,无需为不同平台开发不同版本的应用程序。
总之,微信小程序相比APP具有无需下载安装、快速加载、无需更新、便于分享、轻量级、开发成本低、跨平台等优势。
因此相对于小程序 来说,app 如果想更好的发挥转化使用率等因素,就需要在安装包上下功夫了。
官方数据
此外包体大小每上升6MB,应用下载转化率就会下降1%
数据来在2019谷歌开发者大会上
谷歌给出了一个很详细的数据,包体大小每上升6MB,应用下载转化率就会下降1%。不同地区转化率略有差异,APK包体大小每减少10MB ,全球平均下载转化率会提升1.75%,新兴国家代表印度和巴西下载转化率提升2.0%以上,高端市场代表美国和德国下载转化率提升1.5%。
其他因素
developer.apple.com/help/app-st...
App Store OTA 下载大小限制
苹果公司为了避免用户超出运营商套餐流量,限制了用户通过流量从 AppStore 下载 App 的最大大小, 简称 OTA 下载大小限制。其历史沿革:
- 2017 年 9 月,限制从 100MB 提升到了 150MB;
- 2019 年 5 月下旬,苹果把 OTA 下载限制放宽到 200MB;
- iOS 13 发布之后 iOS13 及以上用户可以使用流量下载超出 200MB 的 App, 但需要用户「设置」选择策略,默认为「超过 200MB 请求许可」,而 iOS13 以下用户仍然无法下载。
除了上边的限制之外 Apple 对可执行文件__TEXT 段的限制则更为严苛,如果超出这个限制 APP 将无法通过 AppStore 审核。这个限制简而言之,如果要支持 iOS8 的设备, App 单架构主二进制 __TEXT 段上限为 60MB(以 1000KB 为 1M,而不是 1024),放弃对 iOS8 的支持二进制大小限制则变为安装包内最大二进制所有架构的总和不超过 500MB。
安装包大小增长的影响
AppStore 下载大小如果在 OTA 下载限制内增长,对用户新增、留存等指标影响不大。而一旦超过 OTA 下载限制,则对整体指标产生明显影响。之前统计的劣化数据指标:当限制在 150MB 并且无法下载的时候,对用户的新增有 10%的影响。由于 iOS13 限制的宽松化,所以在 iOS13 之后设备上这个数据将低于 10%。此数据仅供参考并不能一概而论,对于不同类型的 App 首次安装的场景会呈现差异,比如生活服务、出行类 App 对应蜂窝下载场景会多于影音类、游戏类 App。
其次对仍然需要支持 iOS8 以下的 App, 超出 __TEXT 段大小的限制将会很大程度上影响审核以及发版进度。当然可以通过一些手段进行救急,比如拆分动态库的方式绕过。但是这些手段可能导致安装包整体变得更大。
除了 Apple 的限制外,包大小的劣化一定程度上意味着更加慢的启动速度;更多的的代码逻辑;更低研发效率;过于复杂的代码还会带来对代码修改的风险将对稳定性产生负面影响;让性能等基础体验变差,所以包大小不是一个孤立的指标,它从侧面的反映出 App 的健康状态。
综上,对于用户来说安装包大小是一个非常重要的指标,安装包资源大小优化是非常重要的。
二.苹果对安装包做的优化
App Store和操作系统优化了iOS、tvOS和watchOS应用程序的安装,根据用户特定设备和操作系统版本的功能定制应用程序交付,以最小的占用空间。这种优化被称为应用精简,允许你创建使用最多设备功能,占用最小磁盘空间,并适应苹果未来可能应用的更新的应用。更快的下载速度和更多的空间为其他应用程序和内容提供了更好的用户体验。
Slicing (iOS, tvOS)
针对不同目标设备和操作系统版本创建和发布应用包变体的过程。变体只包含目标设备和操作系统版本所需的可执行体系结构和资源。你可以继续开发并将应用的完整版本上传到app Store Connect。App Store会根据你的应用支持的设备和操作系统版本,创建并发布不同的版本。使用资产目录,以便App Store可以为每个变体选择图像,GPU资源和其他数据。当用户安装应用程序时,将下载并安装用户设备和操作系统版本的变体。
在运行iOS和tvOS 9.0及更高版本的设备上支持切片应用。否则,App Store会向用户提供通用版本。通用版本也可以通过移动设备管理(MDM),通过Apple School Manager或Apple Business Manager批量购买的应用程序,或使用iTunes 12.6或更早版本下载的应用程序提供。
对于在 AppStore 发布的包,苹果也为 App 提供了很多优化方式,而这些是通过企业证书签发的包无法做到的。
当 App 构建完安装包之后上传到 AppStore Connect 后, AppStore Connect 会根据设备、系统来创建其变体(variant)以适配不同的设备,用户从 App Store 中下载到的安装包时候,只下载自己设备用到变体。
变体之间的差异取决于设备的处理器架构(arm64, armv7)、屏幕分辨率(2x, 3x)、iOS 系统版本。
当然这也导致很难用线下构建的安装包来量化最终对下载大小的影响。
Bitcode
位码是已编译程序的中间表示形式。你上传到App Store Connect的包含位码的应用程序将被编译并链接到App Store上。包含位码将允许苹果在未来重新优化你的应用二进制,而无需向app Store提交新版本的应用。
对于iOS应用程序,比特码是默认的,但也是可选的。对于watchOS和tvOS应用程序,需要位码。如果你提供了bitcode,那么app bundle中的所有应用和框架(项目中的所有目标)都需要包含bitcode。
Xcode默认隐藏应用程序的符号,所以苹果无法读取它们。当你将应用上传到app Store Connect时,你可以选择包含符号。当你使用TestFlight或通过app Store发布应用时,包含符号可以让苹果为你的应用提供崩溃报告。如果你想自己收集和标记崩溃报告,你不需要上传符号。相反,您可以在发布应用程序后下载位码编译dSYM文件。
按需资源(iOS、tvOS)
按需资源是指可以用关键字标记并按标记分组请求的资源(例如图像和声音)。App Store托管苹果服务器上的资源,并为你管理下载。App Store还按需分割资源,进一步优化应用的变体。
按需资源提供更好的用户体验:
-
应用尺寸更小,所以下载速度更快,改善了首次发布的体验。
-
当用户浏览应用程序时,根据需要在后台下载按需资源。
-
操作系统在不再使用或磁盘空间不足时清理按需资源。
例如,应用程序可以将资源划分为级别,并仅在应用程序预计用户将移动到下一级别时请求下一级别的资源。同样,只有当用户进行了相应的应用内购买时,应用才能请求应用内购买资源。
综上苹果架构优化主要体现在
关于指令集
x86_64 和 i386,是用于模拟器的芯片指令集架构文件;arm64、armv7、armv7s ,是真机的芯片指令集架构文件。
-
使用 App Thinning 后,用户下载时就只会下载一个适合自己设备的芯片指令集架构文件。App Thinning 有三种方式,包括:App Slicing、Bitcode、On-Demand Resources。
-
App Slicing,会在你向 iTunes Connect 上传 App 后,对 App 做切割,创建不同的变体,这样就可以适用到不同的设备。
-
On-Demand Resources,主要是为游戏多关卡场景服务的。它会根据用户的关卡进度下载随后几个关卡的资源,并且已经过关的资源也会被删掉,这样就可以减少初装 App 的包大小。
-
Bitcode ,是针对特定设备进行包大小优化,优化不明显。
三.优化准备工作
分析安装包组成
四.技术方案
资源优化
大资源优化
资源是指plist、js、css、json、端智能模型文件等,因这些文件和图片在优化方式差异很大,所以把两者区分开来。获取大资源主要途径是递归遍历ipa包的所有资源,体积大于指定阈值(一般20-40kb)的文件就是需要对其优化的文件
通过扫描可以看到对应超过阈值的文件:
此外可以对其他大资源,比如皮肤资源,h5缓存文件等采用下载zip包方式获取,下载成功后合适的时机解压。
- 异步下载:只要APP首次启动时不需要加载该资源,或者即使首次启动需要加载但是使用频率不高,那么该资源就可以走异步下载;
- 资源压缩:当APP首次启动需要加载且频率较高的情况下,可以对大块资源先进行压缩内置APP,启动阶段异步线程解压再使用;
无用配置或者图片文件
针对特定的配置文件类型非必要的进行扫描,自己控制类型,如扫描图片和 html 文件目录是否有其他类型文件
针对图片资源首先将png、webp、gif、jpg排除掉,JS&CSS资源是一般HTML加载的,对于需要扫描图片资源目录通过排除
重复资源优化
指定目录或者从iPA包中获取所有资源文件,通过MD5判断资源是否重复
图片优化
无用图片优化: 基于开源工具扫描
LSUnusedResources
如何扫描的
-
在文件夹[imageset, launchimage, appiconset, bundle, png]中获取资源文件(默认:[imageset, jpg, png, gif])。
-
使用正则表达式搜索代码文件中的所有字符串名称(默认值:[h, m, mm, swift, xib, storyboard, strings, c, cpp, html, js, json, plist, css])。
-
从资源文件中排除所有使用的字符串名称,我们得到所有未使用的资源文件。
Asset Catalog图片优化
之前在bundle需要放二倍图和三倍图,同一张图片最后在用户手机上会有两份,iOS7系统有了Asset Catalog后,Asset Catalog为不同类型设备(分辨率不同)或者相同类型设备但不同配置(磁盘不同)提供定制化资源下载,当用户下载App时,只有跟用户手机硬件设备参数相匹配的资源才会被下载,其他不会下载,从而降低下载包体积;
优势
-
包体积瘦身
-
统一的图片无损压缩
-
便利的资源管理
-
高效的I/O操作
苹果官方对比:
HEIC图片优化
更改PNG和JPEG图片编码格式,选择HEIC方案,基于以下优点:1、体积最小,HEIC比PNG体积减少50%,WebP比PNG优化30%;2、解码效率高,跟WebP相比,HEIC硬解码效率高,略慢于JPEG;
HEIC(High Efficiency Image Coding)是一种图像编码标准,它可以极大提升压缩率,并有效减小储存占用,自iOS 11和macOS High Sierra(10.13)开始,苹果将HEIC设置为图片存储的默认格式,它由动态影像专家小组(MPEG)开发,并在MPEG-H Part 12(ISO/IEC 23008-12)中定义,以下是HEIC图片的特点:
压缩率高:HEIC图片比JPEG图片压缩率高1.5倍,比PNG图片压缩率高3倍,也比GIF图片压缩率高3倍。
节省内存:HEIC图片比JPEG图片节省20%的存储空间,比PNG图片节省50%的存储空间,比GIF图片节省80%的存储空间。
解码效率高:在iOS系统中,HEIC采用硬解码,解码效率高,跟WebP(软编码)相比,是其100倍,但略慢于JPEG。
保留原始图像质量:HEIC图片采用H.264和JEP格式压缩,可以保留原始图像质量。
支持无损放大:HEIC图片支持无损放大,可以将图片放大两倍而不失真。
色彩处理方面: HEIC图片可以根据像素点的亮度分布自动亮度、对比度和饱和度,从而更好地还原图像的真实色彩。
系统兼容性好:我们知道iOS 11开始HEIC是图片存储的默认格式,也就是iOS 11以后的系统都支持HEIC图片,对这部分用户如何处理?在实践中发现,在iOS10系统上,当把HEIC图片放xcasset文件里,最后图片也是可以正常显示的,用Asset Catalog Tinkerer工具解压出Assets.car 文件,发现在xcasset里的HEIC图片,对于iOS10的系统,在打包时会被系统转化为png格式图片,Asset Catalog解决了HEIC图片的兼容性问题。
如何使用
代码加载和png无差异
ini
UIImageView *img = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"test_image_h"]];
[self.view addSubview:img];
img.frame = CGRectMake(100, 100, 200, 100);
- 对于大图HEIC格式明显体积小
理论上来说,HEIC格式图片的体积是PNG格式图片的三分之一,但实际过程发现对于大图,这个优化效果很明显,但是对于小图尤其是小于10K的图片,HEIC图片还有可能超过PNG格式图片,所以我们在做HEIC图片编码优化时,对于小图不建议用这种方式。
- 带有Alpha通道的PNG图片不要做有损压缩
在实践过程中发现,一张PNG原图,尤其是带有Alpha通道,经过有损压缩(TinyPng或ImageOptim)后,再生成HEIC图片时,在iOS12,13,14系统上会显示绿幕,所以带有Alpha通道的PNG图片不要做有损压缩,存在兼容性问题。
转换完成最好要测试下保证不会出现异常加载失败的问题
WebP****压缩优化
HEIC是iOS12以后推出来一种新格式,对于带有Alpha通道的并经过压缩的PNG图片,转换为HEIC格式后,在iOS12和13系统存在兼容性问题,Alpha通道全变为0,为此对于这种case,尤其是大图,如果图片大小超过了 100KB,可以考虑使用 WebP可以采用WebP压缩优化;
来将其他图片转成 WebP
Google开发的
developers.google.com/speed/webp/...
腾讯开发的
转换完毕后显示图片时使用 libwebp 进行解析
对于小于 100KB 时,你可以使用网页工具 TinyPng 或者 GUI 工具ImageOptim进行图片压缩。
TinyPng压缩
WebP 在 CPU 消耗和解码时间上会比 PNG 高两倍,因为对于大于100KB的图片可以使用 WebP,对于小于 100KB 图片,使用TinyPng进行压缩,虽然压缩率没有 WebP 那么高,但是没有改变图片编码方式,所以不会增加解析性能损耗。
编译器优化 & xcode架构优化
xcode14优化
Xcode 支持 ****XCode14的升级对包体积带来比较明显的优化,官方给出数据是应用程序下载包体积减小了 30%
编译器优化
代码优化
LinkMap检查每个类占用大小
https://github.com/jayden320/LinkMap
Link Map 是 Mach-O 格式的二进制文件的一种辅助文件,它描述了可执行文件的全貌,包括编译后的每一个目标文件的信息以及它们在可执行文件中的代码段、数据段存储详情。通过Link Map文件,我们可以知道可执行文件的路径、CPU架构、目标文件、符号等信息,分析可执行文件中哪个类或库占用比较大,进行安装包瘦身,此外,我们可以清楚地了解可执行文件的内部结构和各个目标文件在其中的位置关系,这对于分析和调试非常有帮助。
Mach-O 查看
https://sourceforge.net/projects/machoview/
Object文件列表
Object文件列表列出了所有编译后的目标文件,包括.o文件和dylib库。每个目标文件都有一个对应的编号,通过该编号可以对应到具体的类,Symbols会用到此编号。
Section段表
section段表描述了各个段在最终编译成的可执行文件中的偏移位置及大小,包括代码段(TEXT)和数据段(DATA)。段表中第一列是数据在文件的偏移位置,第二列是Section占用大小,第三列是Segment类型,第四列是Section类型,关于Segment和Section
2.3.4 Symbols
Symbols模块给出了类里面的方法在内存具体情况。其中
- 第一列是方法起始地址,通过这个地址我们可以查上面的段表;
- 第二列是大小,通过这个可以算出方法占用的大小;
- 第三列是归属的类(.o),值是具体编号,通过反查目标文件列表可以知道对应的类;
- 第四列是方法名称。
通过Symbols模块我们可以分析出来每个类对应方法的大小。
静态检查
58开源的工具
利用appcode 工具检测无用的代码
用 AppCode 做分析的方法很简单,直接在 AppCode 里选择 Code->Inspect Code 就可以进行静态分析。
-
无用类:Unused class 是无用类,Unused import statement 是无用类引入声明,Unused property 是无用的属性;
-
无用方法:Unused method 是无用的方法,Unused parameter 是无用参数,Unused instance variable 是无用的实例变量,Unused local variable 是无用的局部变量,Unused value 是无用的值;
-
无用宏:Unused macro 是无用的宏。
-
无用全局:Unused global declaration 是无用全局声明。
appcode缺点
-
某些库里定义了未使用的协议会被判定为无用协议
-
如果子类使用了父类的方法,父类的这个方法不会被认为使用了;通过点的方式使用属性,该属性会被认为没有使用;使用 performSelector 方式调用的方法也检查不出来,比如 self performSelector:@selector(arrivalRefreshTime);
-
运行时声明类的情况检查不出来。比如通过 NSClassFromString 方式调用的类会被查出为没有使用的类,比如 layerClass = NSClassFromString(@"SMFloatLayer")。还有以[[self class] accessToken] 这样不指定类名的方式使用的类,会被认为该类没有被使用。像 UITableView 的自定义的 Cell 使用 registerClass,这样的情况也会认为这个 Cell 没有被使用。
-
使用 AppCode 检查出来的无用代码,还需要人工二次确认才能够安全删除掉。
动态检查
运行时检查类是否真正被使用过
scss
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
isInitialized 的结果会保存到元类的 class_rw_t 结构体的 flags 信息里,flags 的 1<<29 位记录的就是这个类是否初始化了的信息。而 flags 的其他位记录的信息,objc runtime 的源码,如下:
arduino
// 类的方法列表已修复
#define RW_METHODIZED (1<<30)
// 类已经初始化了
#define RW_INITIALIZED (1<<29)
// 类在初始化过程中
#define RW_INITIALIZING (1<<28)
// class_rw_t->ro 是 class_ro_t 的堆副本
#define RW_COPIED_RO (1<<27)
// 类分配了内存,但没有注册
#define RW_CONSTRUCTING (1<<26)
// 类分配了内存也注册了
#define RW_CONSTRUCTED (1<<25)
// GC:class有不安全的finalize方法
#define RW_FINALIZE_ON_MAIN_THREAD (1<<24)
// 类的 +load 被调用了
#define RW_LOADED (1<<23)
五.其他流程阶段预防
1.开发阶段图片大小把关
使用前压缩,命名规范,xcassets统一管理图片
2.通过Jenkins打包监控对比包大小增长
总结:
通过上述对包大小优化的背景和收益的理解,再到结合工具和流程方案找出其短板,根据自己项目的情况合理采取对应方案持续优化关注,才是需要长期坚持做下去的。
感谢参考