写点什么

百度 APP iOS 端包体积 50M 优化实践 (二) 图片优化

作者:百度Geek说
  • 2023-04-25
    上海
  • 本文字数:7261 字

    阅读完需:约 24 分钟

一、前言

在上一篇文章,我们介绍了包体积优化的必要性、安装包组成部分和生成过程、国内外大厂 APP 包体积分析、百度 APP 包体积优化技术方案及各项收益,本文重点讲述图片优化,解压 IPA 包后发现,百度 APP 中 asset 和 bundle 里面图片共有 94M,这是我们重点优化的对象。


本系列文章目录如下:


《百度APP iOS端包体积50M优化实践(一)总览》(点击标题即可跳转)


百度 APP 采用如下方式对不同的图片资源进行了优化:


第一、无用图片优化,解决的是随着版本迭代,一些图片已经没有了引用关系,但还在 IPA 包中保留,挖掘这部分图片并删除,这个优化是所有包体积优化项目中 ROI 最高的,影响范围局限在单个组件内,质量可控,关键是提高查找无用图片的准确度;


第二、Asset Catalog 优化:使用 Asset Catalog 管理的图片能被 App Store 工具 App Thinning 处理,处理后用户只会下载匹配其设备分辨率的图片资源,从而降低了用户下载的包体积;


第三、HEIC 图片优化:跟 PNG、JPEG、WebP 相比,HEIC 图片编码格式体积减少最小,从解码效率角度来说,跟 WebP 相比,HEIC 硬解码效率高。

二、无用图片优化

2.1 方案综述

首先获取所有图片资源,然后开发工具获取 Objective-C、Swift、xib、storyboard、html、js、css、json、plist 文件可能引用图片的静态字符串,接着前面两个集合做 diff 即可排查未引用的图片,最后针对字符串拼接的常见 case 做二次过滤,覆盖的 case 越多准确度越高,当然也要考虑 ROI。

2.2 获取所有图片

开启每个库的源码,用脚本检测所有图片及图片所属关系,为后续分发及落地优化提供方便。如果不开源码使用二进制库或者 ipa 包会带来很多麻烦,如获取 asset.car 里面的图片资源比较困难,同时只能知道图片名称,不能直接获取图片属于哪一个库。开启每个库的源码后,用脚本递归遍历可获取所有图片,从图片路径可知道所属关系,参考代码如下所示:



def findAllPictures(path): pathDir = os.listdir(path) for allDir in pathDir: child = os.path.join('%s%s' % (path, allDir)) if os.path.isfile(child): # 获取读到的文件的后缀 end = os.path.splitext(child)[-1] if end == ".png" or end == ".webp" or end == ".gif" or end == ".jpeg" or end == ".jpg": print("文件" + child + " 后缀 " + end) else: # 递归遍历子目录 child = child + "/" findAllPictures(child)
复制代码

2.3 获取可能引用图片静态字符串

在这个环节,我们重点是要找到在代码中可能会引用图片的字符串集合,如在 Objective-C 的.m 文件中,我们经常用如下代码去加载图片 account_login 来创建一个 UIImageView 对象, 针对 Objective-C 的.m 文件内容,用正则过滤,匹配表达式为 @"(.*?)",即可获取所有可能加载图片的字符串集合。


imgView.image = [UIImageimageNamed:@"account_login"];br
复制代码


对于 Swift 文件我们通常通过如下代码去加载图片 account_login,加载方式完全不一样,针对 Swift 这种文件,正则表达式应为"(.?)"。


let imageView = UIImageView(frame: CGRectMake(100, 10, 200, 200))imageView.image = UIImage(named:"account_login.jpg")self.view.addSubview(imageView)
复制代码


对于 html 文件我们通常用如下代码去加载图片,正则表达式应为 img\s+src="'["']。


<html><body><img src="图片地址" alt="文本说明" width=** height=**></body></html>
复制代码


不同的文件加载图片的方式不同,如 Objective-C、Swift、xib、storyboard、html、js、plist、json 和 css 都不尽相同,在下面的表格,我整理出常用文件过滤图片使用的正则表达式。


2.4 获取未引用图片

通过 2.2 章节我们获取的工程中的所有图片,通过 2.3 章节我们获取代码中所有可能引用图片的静态字符串,那么对于每张图片而言,如果不在引用图片的静态字符串集合中,这张图片可能就是未引用的图片。

2.5 字符串拼接的常见 case 做二次过滤

经过上面的环节,我们通过全字符串匹配获取了未引用的图片,实际开发过程,有一些常见情况,代码中引用图片的名称是通过字符串拼接生成的,因此我们需要进行二次过滤。第一种常见的 case,就是当手机支持暗黑模式时,同一个位置的图片通常会有两套,通过后缀如 light、dark 或者 day、night 来做区分;第二种常见的 case,就是后缀是数字的图片序列,iOS 端有一种动画类型是 ImageView 加载多图动画,图片名称是由字符串和动态数字后缀拼接而成,过滤时需要覆盖如下后缀_%d,_%ld,_%zd,_%lu。

三、Asset Catalog 图片优化

3.1 背景

Asset Catalog 是 Xcode 提供的在 iOS7 系统开始引入的资源管理工具,将分散在项目中大大小小的资源进行统一存放和集中管理,包括但不限于 images、sprites、textures, ARKit resources 和 PDF,我们可以把之前放在 bundle 的图片或者其他资源放入 Asset catalog 中,XCode 最后统一压缩成一个 Assets.car 的文件。


在 Asset Catalog 之前,我们通常将图片直接放到工程的 bundle,这种方式存在一些缺点:第一、空间浪费,不同设备需要不同分辨率图片,所以在 bundle 里对同一张图片同时存在二倍图和三倍图,浪费资源;第二、图片压缩只能针对单个文件,没有统一的压缩功能;第三、信息冗余,每个图片资源都会存储自己的元数据和其他的一些属性信息,如果存在很多同类型的资源,这些相同的信息会产生冗余,造成空间浪费。

3.2 Asset Catalog 优点

针对 bundle 存在的上述问题,Asset Catalog 做了诸多优化,无论是包体积优化、统一的图片压缩和便利的资源管理,还是高效的 IO 操作,每一项优化都做到了极致,下面详细介绍:


第一、包体积瘦身:Asset Catalog 为不同类型设备(分辨率不同)或者相同类型设备但不同配置(磁盘和内存不同)提供定制化资源下载,之前在 bundle 需要放二倍图和三倍图,同一张图片最后在用户手机上会有两份,有了 Asset Catalog 后,当用户下载 App 时,只有跟用户手机硬件设备参数相匹配的资源才会被下载,其他不会下载,比如说,iphone8 手机用户只会下载二倍图片,iphone13 的用户只会下载三倍图片,这样可明显减少下载包大小。


第二、统一的图片无损压缩:Asset Catalog 默认对文件夹中的所有图片采用无损压缩,压缩方法是 Apple Deep Pixel Image Compression,这是苹果新引入的一种压缩形式,会根据图片的色谱特性选择最优的算法进行压缩,压缩比能提高 15~20%。WWDC2018:Optimizing App https://developer.apple.com/videos/play/wwdc2018/227/ 有详细介绍。具体来说,针对不同类型的图片采用有不同的优化方式,一类是简单的图片资源,如很多 icon 图片,这类图片只有相对简单的配色和设计。另一类指的是复杂的图片资源,Apple Deep Pixel Image Compression 针对这两种形式都做了不同形式的优化,图片资源体积越大使用 Asset Catalog 后优化的效果就越明显,统一的压缩更有利于实现包体积瘦身,下面这张图是苹果官方给出的 Apple Deep Pixel Image Compression 体积压缩比的优化。



第三、便利的资源管理:如果将图片直接放在工程目录下面,项目打包后图片文件是散落在 iPA 包里面,而如果用 Asset Catalog 来管理放在 xcassets 中,在打包后会将这些图片统一压缩成一个 Assets.car 的文件。


第四、高效的 I/O 操作:Assets Catalogs 图片加载耗时比普通的 bundle 加载图片耗时要少两个数量级,这是因为编译最后生成 Assets.car 文件包含了 BOM 文件,BOM 文件提供了图片加载时需要的的 rendition、renditionKey 和 attribute 属性值,rendition 是 CoreUI.framework 对某一图像资源的不同样式的统称,如 @2x,@3x,每一个 rendition 有一个 renditionKey 与之对应,通过 renditionKey 获取到对应的 attribute,attribute 中包含了各种属性,如图片的分辨率、垂直大小、水平大小等参数。


用 Assets Catalogs 管理的图片,通过 imageNamed 方法进行加载,因为 car 文件里面的上述资源信息是 XCode 编译时生成好的,当解析完 car 文件后,可以直接通过图片名称获取 renditionKey 和 attribute 属性并读取图片资源,没有任何多余操作,相反对于用 bundle 管理的图片,额外的操作太多导致耗时严重。对于 bundle 管理图片有两种加载方式,第一种通过 imageName 方式加载图片,需要先去 Assets.car 里面查询,由于图片资源并不在 Assets.car 里面,所以在获取 rendition 和 renditionKey 时多次调用 canGetRenditionWithKey,canGetRenditionWithKey,最后再重新通过 mmap 加载读取图片属性和图片资源,形成 rendition 和 renditionKey,总体耗时最大;第二种通过 imageWithContentsOfFile 加载图片,不需要去 Assets.car 里面查询,没有生成 rendition 和 renditionKey 的相关操作和缓存操作,只有读取图片属性和图片二进制的操作耗时,但是没有图片属性等相关缓存所以耗时比较长。

3.3 Assets.car 生成过程

具体来说,Xcode 在处理 Asset Catalog 节点时, 构建 Asset Catalog 的工具 actool 会先对 Asset Catalog 中的 png 图片进行解码,得到 Bitmap 数据,然后再运用 actool 的压缩算法进行编码压缩处理生成 Assets.car 文件,这就可以解释在 Asset Catalog 中放 jpg 格式图片,最后生成的 Assets.car 文件中却是 png 格式图片。

3.4 Assets.car 操作

用 Assets Catalogs 管理的图片,XCode 编译时并不是简单的拷贝操作,而是将所有资源打包生成的 Assets.car 文件,这是一种压缩文件,直接解压无法操作的,利用 XCode 自带工具 assetutil 可以分析.car 文件,分析命令如下所示:


sudo xcrun --sdk iphoneos assetutil --info ./Assets.car > ./AssetsInfo.js
复制代码


通过 AssetsInfo.json 获取图片相关属性,但是无法获取里面的图片。



如果想将 car 文件中的图片提取出来,推荐一个开源工具叫 Asset Catalog Tinkerer,可以从 github 下载,这里给出 github 地址:https://github.com/insidegui/AssetCatalogTinkerer


3.5 Asset Catalog 的压缩算法

使用 3.4 节介绍的 XCode 自带工具 assetutil 可以知道每张图片的压缩算法,Compression 字段值代表不同图片的采用的不同压缩算法,通过实践发现 actool 支持的压缩算法有 deepmap2、deepmap_lzfse、zip、lzfse、palette_img,具体采用哪种压缩算法跟很多因素有关,如图片自身特性、打包的 XCode 版本、Framework 支持的 iOS 最低版本、编译配置(Asset Catalog Compiler - Options Optimization),从实际效果来看,XCode 会根据综合上述因素选择一个压缩比最优的算法,另外这些压缩算法都是无损的。


3.6 不要做无损压缩

开发者在图片放入 Asset Catalog 之前千万不要做无损压缩,无损压缩算法是通过改变图片的压缩编码算法达到减少体积大小的目的,不会改变解码后的 Bitmap 数据,从 3.3 节中我们知道 Assets.car 文件的生成过程中,Asset Catalog 的工具 actool 先做解码得到 Bitmap 数据,然后再编码压缩处理,针对无损压缩算法 actool 接收的 Bitmap 数据并没有改变,所以无损压缩无法优化包体积,UI 设计师给出的 PNG 图片如果采用 Asset Catalog 优化就千万不要做无损压缩。

3.7 bundle 多倍图片 Asset 优化

Asset Catalog 是 Apple 在 2013 年发布的 iOS7 的系统开始引入的,从 iOS 9 之后开始支持做资源管理,老代码(尤其是 16 年前的代码)都是用 bundle 的方式去管理图片,为此,我们开发脚本专门针对 bundle 多倍图片做检查,然后采用 Asset 优化,这种优化方式可以实现包体积立减一半,参考脚本如下所示:



def find_all_bundle_pic(app_package_path, all_pic_list): """ 将所有bundle图片存入list中 """ pathDir = os.listdir(app_package_path) for child_file in pathDir: child_path = os.path.join('%s/%s' % (app_package_path, child_file)) # isfile:如果child是一个存在的文件则返回true,否则(bundle、文件夹会等)返回false if os.path.isfile(child_path): if child_path.endswith(".png") or child_path.endswith(".jpg") or child_path.endswith(".jpeg") or child_path.endswith(".gif") or child_path.endswith(".webp"): if child_path.find(".bundle") > 0: all_pic_list.append(child_path) else: find_all_bundle_pic(child_path, all_pic_list)
def find_opt_pic(all_picture_list,final_opt_pic_list): """ 查找bundle中重复的多倍图片 """ for picture in all_picture_list: if picture.endswith("@2x.png"): prefix_2x = picture[0: len(picture) - 7] for picture1 in all_picture_list: # 前缀匹配 if picture1 != picture and picture1.startswith(prefix_2x): if (len(picture) == len(picture1) and picture1.endswith("@3x.png")) or len(picture) == len(picture1) + 3: final_opt_pic_list.append(picture) final_opt_pic_list.append(picture1)
复制代码

3.8 有损图片压缩可减少 Assets.car 大小

从 3.6 节我们得知无损压缩对于 Asset Catalog 是没有体积优化效果的,但是有损压缩可减少 Assets.car 大小,因为 Asset Catalog 自身也会对图片进行压缩优化,所以有损压缩图片的收益没有 bundle 转 Asset Catalog 收益明显,常用的有损压缩工具有 TinyPng 和 pngquant。


TinyPng 是一个网页版的工具,通过合并图片中相似的颜色,将 24 位的 PNG 图片压缩成 8 位色值的图片,并且去掉了图片中不必要的元数据来实现压缩。对于单张图片压缩使用非常方便,链接地址如下:https://tinypng.com/ ,但是如果要处理批量图片压缩,上传过程中容易出现上传不成功等问题,这个工具不支持自定义压缩配置。


pngquant 是一个有损的 PNG 压缩开源库,提供了命令行和源码库两种形式。将 24 位或 32 位的 RGBA PNG 图转换成 8 位 PNG 图并保留透明度通道。通过这个库的转化可以显著减少 png 文件大小,pngquant 采用的是本地脚本压缩,工具下载地址:https://pngquant.org ,对批量压缩图片支持的比较友好,pngquant 支持自定义压缩品质,配置压缩品质小于 90 后压缩率会高于 TinyPng,并且 pngquant 是开源的,可以自定义,这是百度 APP 图片压缩的首选。

四、HEIC 图片编码优化

4.1 HEIC 图片编码优点

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 图片,但我们百度 APP 目前还支持 iOS10 系统,对这部分用户如何处理?在实践中发现,在 iOS10 系统上,当把 HEIC 图片放 xcasset 文件里,最后图片也是可以正常显示的,我们做了一番原因排查,用 Asset Catalog Tinkerer 工具解压出 Assets.car 文件,发现在 xcasset 里的 HEIC 图片,对于 iOS10 的系统,在打包时会被系统转化为 png 格式图片,Asset Catalog 解决了 HEIC 图片的兼容性问题。

4.2 如何生成 HEIC 图片

利用 Mac 自带功能实现 png 转 HEIC 方法:右键图片,快速操作-》转换图像, 格式选 HEIF,选择原图像。



优化结果如下所示,左边是 png 原图(1.6M),右边是 HEIC 图片(106KB)。


4.3 HEIC 图片使用方法

4.3.1 必须在 Asset Catalog 使用

HEIC 图片必须放在 Asset Catalog 中才能使用,bundle 方式不支持 HEIC 图片加载。



HEIC 图片的加载使用方法和普通的 asset 图片一样,如下所示:


imgView.image = [UIImage imageNamed:@"account_login"];
复制代码

4.3.2 对于大图 HEIC 格式明显体积小

理论上来说,HEIC 格式图片的体积是 PNG 格式图片的三分之一,但实际过程发现对于大图,这个优化效果很明显,但是对于小图尤其是小于 10K 的图片,HEIC 图片还有可能超过 PNG 格式图片,所以我们在做 HEIC 图片编码优化时,对于小图不建议用这种方式。

4.3.3 带有 Alpha 通道的 PNG 图片不要做有损压缩

在实践过程中发现,一张 PNG 原图,尤其是带有 Alpha 通道,经过有损压缩(TinyPng 或 ImageOptim)后,再生成 HEIC 图片时,在 iOS12,13,14 系统上会显示绿幕,所以带有 Alpha 通道的 PNG 图片不要做有损压缩,存在兼容性问题。

五、总结

图片优化是包体积优化的重头戏,百度 APP 经过两个 Q 的优化落地 9.75M 的收益解决了存量图片的问题,随后建立图片使用规范和无用图片检测流水线解决增量图片的问题。


本文详细介绍了无用图片检测方案、Asset Catalog 图片优化和 HEIC 图片优化方案,后续我们会针对其他优化类型详细介绍其原理与实现,敬请期待。


——END——


参考资料:


[1]Asset 使用方法:https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/index.html#//apple_ref/doc/uid/TP40015170-CH18-SW1


[2]Asset 介绍:https://help.apple.com/xcode/mac/current/#/dev10510b1f7


[3]WWDC2018:Optimizing App Assets:https://developer.apple.com/videos/play/wwdc2018/227/


[4]TinyPng:https://tinypng.com/


[5]pngquant:https://pngquant.org


[6]HEIC 图片介绍:https://mobiletrans.wondershare.com/heic-convert/what-is-heic-file.html


推荐阅读:


浅论分布式训练中的recompute机制


剖析多利熊业务如何基于分布式架构实践稳定性建设


百度工程师的软件质量与测试随笔


百度APP iOS端包体积50M优化实践(一)总览


基于FFmpeg和Wasm的Web端视频截帧方案


百度研发效能从度量到数字化蜕变之路

发布于: 刚刚阅读数: 3
用户头像

百度Geek说

关注

百度官方技术账号 2021-01-22 加入

关注我们,带你了解更多百度技术干货。

评论

发布
暂无评论
百度APP iOS端包体积50M优化实践(二) 图片优化_ios_百度Geek说_InfoQ写作社区