前段时间APP要做资源压缩,需要把项目中使用的所有图片资源进行压缩,以减小APP安装包体积。想着既然压缩APP资源是为了缩小APP体积,那么来一遍APP整体瘦身流程并做一下总结吧。
整个过程分三步:
1.瘦身前分析
2.瘦身策略制定并实施
3.结果对比
瘦身前分析
安装包分析
iOS安装包有两种状态:一种是提交给Apple市场的多架构综合包;另一种是用户下载的单架构特定包;
提交给Apple市场的多架构综合包是Xcode构建出的产物ipa包,它里面包含了多个架构的产物,多套尺寸的图片资源,当上传成功后苹果会进行裁剪和二次分发,转化为供用户下载的具体架构的App Store下载ipa包。
用户下载的单架构特定包是用户从App Store下载的包。它里面包含了当前手机架构的静态库二进制文件、动态库、asset.car(单尺寸)、其他资源文件。它是针对当前手机的架构的特定下载包。里面的资源都是单份的。
对于多架构综合包可以使用工具对特定架构进行拆分
使用lipo工具拆分单架构
lipo "originalExecutable" -thin arm64 -output "arm64Executable"
使用assetutil工具拆分不同倍图的asset
xcrun --sdk iphoneos assetutil --scale 3 --output "$targetFolder/Assets3.car" "$sourceFolder/Assets.car"
源代码分析
iOS工程结构:iOS工程由主工程和Pod模块组成,模块有静态库和动态库两种类型。
主工程的构成有主Target,里面有源代码文件(OC的.h和.m、Swift)、xcassets、nib、bunlde、多语言文件、各种配置文件(plist、json)。
模块内部的构成有源代码文件(OC、C、C++的.h和.m、Swift)、xcassets、nib、bunlde、多语言文件、各种配置文件(plist、json)。
源码->IPA产物
iOS工程项目由源码到IPA包的核心的变化是:编译和文件拷贝。源码会被编译链接为MachO可执行文件,xcassets文件夹会被转化为Assets.car,其他都可以简单理解为文件拷贝。
IPA包产物分析
对从App Store下载的IPA包解压后是一个文件夹,内部主要包括MachO可执行文件、.framework(动态库)、Assets.car、.strings(多语言)、.bundle、nib、json、png...。
可以根据产物资源体积由大到小进行优化,比如:MachO可执行文件(包含所有静态库)、动态库、assets.car、bundle、nib、音频、视频。
瘦身策略
瘦身总体方向可以参下面5个方面入手:组件治理,资源治理,编译优化,代码治理,运行时下载。
组件治理[删除淘汰业务代码]:0代码覆盖率组件,无用组件,重复功能组件。
资源治理[正在用的压缩,不用的删除,太大的网络下载,相似的使用iconfont共用]:大资源,可以有损压缩资源,无用资源,重复图标,iconfont,多语言文案
编译优化[修改编译策略]:精简编译产物,剥离符号表Strip Linked Product,删除未引用的C/C++/Swift代码,Asset Catalog Complier,C++导出必要符号, Symbols Hidden by Default, LTO优化跨模块调用代码
代码治理[删除无用类]: 运行时未加载类,编译时无用类,业务重构
运行时下载[大资源网络下载]:大资源,多语言文案
组件治理[删除淘汰业务代码]
主要看对应模块的业务是否是线上正常运行,还是已经被废弃,或者被其他方式(RN, H5, Flutter)所取代,如果实际不在使用,则删除到此组件。
资源治理[正在用的压缩,不用的删除,太大的网络下载,相似的使用iconfont共用]
是APP瘦身效果非常好的一个方向,通常会有不错的收益。
这里需要注意使用Asset Catalog管理切图资源的包体积反弹问题,因为Xcode在编译时,会使用actool工具对Asset Catalog下的图片资源优化,压缩,这可能导致之前的压缩过的图片被重写变大。
1.有损压缩
XCode构建时会做“compile asset catalog”,会重新对图片进行无损压缩。因此使用imageoptim等工具进行无损压缩效果不明显,其中压缩png图片没有效果,压缩jpg图片有一定效果。
根据实践经验,icon做有损压缩并不影响视觉体验,压缩率可以达到70%~80%。业界有不少png压缩工具,我们使用到的有tinypng、pngquant、pngcrush、optipng(无损)、advpng。
2.删除无用图片 - 未被使用的图片资源
先解析出所有拷贝到构建产物的资源文件,再解析出代码中实际引用到的资源文件,两者的差集就是无用资源。
第一步获取全量资源文件。
a.可以通过在Cocoapos工程中,“Pods-targetName-resource.sh”脚本负责拷贝Pod里的文件资源到构建产物,包括所有文件类型bundle、xcassets、json、png。解析该脚本可以得到每个Pod模块都拷贝了哪些图片资源。
// Pods-targetName-resource.sh
install_resource "${PODS_ROOT}/APodName/APodName.framework/APodName.bundle"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/BPodName.xcassets"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/xxx.png"
b.也可以通过编写shell脚本,扫描项目源文件,找出所有的png,jpg资源文件。
因为我们项目中所有的图片都放在了Asset中进行管理,所以这里只扫描Asset下面的图片资源特征
targetPicture=".imageset"
function searchOneDir(){
cd $1
path=`pwd`
for item in `ls`; do
if [[ -d "${path}/${item}" ]] && [[ ! "${filterList[@]}" =~ "${item}" ]]
then
if [[ "${item}" =~ "${targetPicture}" ]]
then
echo "${path}/${item}"
echo "内部:$item"
itemSub="${path}/${item}"
appContainPictureList+=(${itemSub})
else
if [[ -d ${item} ]]
then
searchOneDir "$item"
fi
fi
fi
done
cd ..
path=`pwd`
echo "path2: $path" return 0;
}
详细脚本见如下地址:
https://github.com/zhfei/ios-scripts/blob/main/tool_project_batchZipPicture.sh
c.或者从IPA包中直接查看
iOS开发中,如果使用了Images.xcassets管理图片,打包的时候会生成一个Assets.car文件,所有的图片都在这里面。
如果想查看里面包含的图片,则需要工具来解压将Assets.car文件解包到指定文件夹,可使用的工具有:cartool,AssetCatalogTinkerer。
第二步,获取代码中实际引用到的资源文件。OC代码中引用资源文件都是以字符串字面量的形式声明,构建后存放在Mach-O文件"__cstring" section。
利用strings解析framework的二进制文件就可以得到代码中所有的字符串声明,进行过滤。
这里有个注意点,对于Swift项目中使用的切图在strings打印的符号表中并不能直接查出,这个问题暂时没有明白为什么。
targetAssetDir=".xcassets"
targetPicture=".imageset" appContainPictureList=()
UnUsedPictureList=()
UsedPictureList=()
function checkMachOFile() {
res=`strings $1` for assertName in ${appContainPictureList[@]}; do
if [[ ! "$res" =~ "$assertName" ]]
echo "$assertName"
then
UnUsedPictureList[${#UnUsedPictureList[*]}]=$assertName
else
UsedPictureList[${#UsedPictureList[*]}]=$assertName
fi
done
}
详细脚本见如下地址:
https://github.com/zhfei/ios-scripts/blob/main/tool_project_findUnUsePicture.sh
3.重复图标
解决方案是在构建时计算资源的哈希值,去重相同哈希资源,并保留源文件名和哈希值的映射表。运行时Hook 资源加载的”imageNamed“方法,根据映射表替换资源名称。
4.iconfont
iconfont支持缩放、修改颜色,它size小,适合用于箭头、占位图等图标场景,使用iconfont可以减少包大小也能提高开发视觉体验的统一性。对相似的场景进行限制,禁止随意添加切图。
5.多语言文案
6.运行时下载
编译优化[修改编译策略]
1.精简编译产物Oz:Optimization Level
Optimization Level多个级别,-Oz比-O3的编译产物体积小10%左右。设置-Oz以后,XCode会优化连续的汇编指令,从而减少二进制大小,但副作用是执行速度会变慢。C++工程建议都开启。
主工程Release
Optimization Level :-Oz
Framework工程
Optimization Level :-Oz
2.Strip Linked Product - 剥离符号表
在Xcode中,"Strip Linked Product"是一个构建设置选项,用于控制在构建过程中是否剥离(strip)可执行文件中的符号信息。
符号信息是一个标识符或者函数名称,在可执行文件或动态链接库中可用于调试和符号化。剥离符号信息可以减小可执行文件的大小,同时也可以防止他人通过分析符号信息来获取敏感信息。
Strip Linked Product设置会剥离特定的符号,Debug环境不要设置YES,否则调试时看不到符号。
在Xcode中,"Deployment Postprocessing"是一个构建设置选项,它用于控制构建完成后是否执行部署后处理。
部署后处理是指构建过程完成对生成的可执行文件或应用程序包进行额外的处理。它可以包括诸如符号剥离、代码签名、资源压缩等操作。
主工程Release
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :All Symbols(剥离所有符号表和重定向信息) Framework工程
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :Non-Global Symbols(剥离包括调试信息等非全局的符号,保留外部符号)
说明:
a.静态库不能将Strip Style 设置为All Symbols,因为剥离了所有符号的静态库是无法被正常链接的
b.去除符号不影响 dSYM 文件中的符号信息,查看崩溃日志时,可以从 dSYM 文件中找对应符号
3.Symbols Hidden by Default
Symbols Hidden by Default用于设置符号默认可见性,如果设置为YES,XCode会把所有符号都定义为”private extern”,包大小会略有减少。动态库设置为NO,否则会有链接错误。
主工程Release
Symbols Hidden by Default :Yes
Framework工程 静态库/动态库
Symbols Hidden by Default :NO
4.剔除未引用的C/C++/Swift代码:Dead Code Stripping
Dead Code Stripping开启后会在链接时移除未使用的代码,它对静态语言C/C++/Swift有效,对动态语言OC无效。
主工程
Dead Code Stripping :Yes
5.Asset Catalog Compiler
Optimization有三个选项,空、time和Space,选择Space可以优化包大小
主工程
Asset Catalog Compiler->Optimization设置为space
6.C++导出必要符号 - 动态库复用主二进制静态库(用的不多,因为很多项目不会嵌入动态库)
C++动态库经常用到一些基础库比如openssl、libyuv、libcurl,他们一般是静态库。如果动态库引用了静态库,它编译时默认会内嵌静态库的所有符号。
虽然我们可以在动态库中设置只导出需要用到的静态库符号,但是有可能多个动态库都用到了同一个基础库,这样还是会造成基础库的冗余。
比如openssl大小1MB,如果A、B两个动态库依赖了openssl,APP也引用了openssl,最终ipa包实际有3个openssl,有2MB大小是冗余的。
这种场景下,最佳解决方案是共享符号表,让动态库可以调用主二进制的基础库符号,从而可以去掉内置的静态库。只要修改XCode的Link配置,无需额外的代码开发。
动态库工程:
1设置当遇到未定义的函数时,动态查找APP主二进制符号表。
2 关闭bitcode
Other Linker Flags -> -undefined dynamic_lookup
Enable Bitcode -> No

3导出动态库需要调用的外部符号,写到一个文件exported_symbols内
nm -u xxx.framework/xxx > exported_symbols.txt

APP工程:
1配置需要导出exported_symbols文件内的所有符号,避免编译时动态库需要用到的符号被strip掉。
2关闭bitcode。
// exported_symbols.txt是需要被导出的符号文件路径
EXPORTED_SYMBOLS_FILE -> exported_symbols.txt
Enable Bitcode -> No
7.C++只导出必要符号:Symbol Visibility (用的不多,因为很多项目没有涉及自己构建C++静态库和动态库)
用到哪部分导出哪部分,没有使用的不导出。
C++的静态库和动态库都只导出必须的符号,默认设置为隐藏所有符号,然后用Visibility Attributes单独控制需要导出的符号。
8.默认隐藏所有C++符号
Other C++ Flags->添加-fvisibility=hidden
设置需要导出的符号
__attribute__((visibility("default"))) void MyFunction1() {}
__attribute__((visibility("default"))) void MyFunction2() {}
代码治理[删除无用类]
删除无用OC类 - 运行时Objc类覆盖率
ObjC的类第一次被使用时会调用+initialize方法,类被加载过后cls->isInitialized会返回True。isInitialized方法读取了metaClass的data变量里的flags,如果flags里的第29位为1,则返回True。
// objc-class.mm
Class class_initialize(Class cls, id inst) {
if (!cls->isInitialized()) {
initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
}
return cls;
} // objc-runtime.h
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
删除无用OC类 - 编译时未被引用的类
iOS编译的产物是Mach-o格式的,文件里 __DATA __objc_classrefs 段记录了所有引用过的类的地址,__DATA __objc_classlist段记录了所有类的地址,两者Differ可以得到未被引用类的地址。
然后将地址符号化,就可以得到未被引用类信息。因为Objc是动态语言,如果使用runtime动态调用某个class,这种情况扫描不出来。(比如Target Action和 JS Core)
otool -v -s __DATA __objc_classrefs xxxMainClient  #读取__DATA Segment中section为__objc_classrefs的符号
otool -v -s __DATA __objc_classlist xxxMainClient #读取__DATA Segment中section为__objc_classlist的符号
nm -nm xxxMainClient
运行时下载[大资源网络下载]
大资源
多语言文案
瘦身结果
项目中切图通过压缩少了7M,Xcode编译产生的APP包少了1.2M。
项目切图资源小了7M,实际打包只少了1.2M的原因为:
对应Asset Catalog下管理的切图资源,Xcode打包编译时会用actool工具处理图片,优化图片大小。对于压缩后的产物,在Xcode编译打包后,被压缩的切图都会重新变大,包括:pngquant有损压缩和imageoptim无损压缩。
解决方法
1.关掉Xcode的PNG优化开关(设置Targets->Build Settings->Compress PNG Files为YES)。
2.可以采用将压缩后的切图放在一个单独的目录下,脱离Asset Catalog的管理,避免被压缩后的切图重新被优化大。
3.修改压缩策略,将Asset Catalog Compiler->Optimization设置为space
另外
为了后续的内存变化检测,可以为每个模块做增量变化统计,静态库和动态库计算的原理不同。
对于静态库,先解析linkmap数据,计算出模块代码大小,在解析Pods-targetName-resource.sh的资源拷贝代码,计算出拷贝到Pod模块的资源大小。
对于动态库,先使用lipo拆分动态库的二进制文件,计算出单架构的代码大小,然后再计算动态库framework内的资源文件,得到动态库的资源文件大小。
参考文章:
https://developer.aliyun.com/article/981881

iOS安装包瘦身总结的更多相关文章

  1. iOS安装包瘦身的那些事儿

    在我们提交安装包到App Store的时候,如果安装包过大,有可能会收到类似如下内容的一封邮件: 收到这封邮件的时候,意味着安装包在App Store上下载的时候,有的设备下载的安装包大小会超过100 ...

  2. iOS 安装包瘦身(下篇)

    本文来自网易云社区 作者:饶梦云 2.4. 清理无用代码 2.4.1. Dead Code Stripping Activating this setting causes the -dead_str ...

  3. iOS 安装包瘦身 (上篇)

    本文来自网易云社区 作者:饶梦云 1. 安装包组成 谈到 App 瘦身,最直接的想法莫过于分析一个安装包内部结构,了解其每一部分的来源.解压一个 ipa 包,拿到其 payload 中 app 文件的 ...

  4. Android APK安装包瘦身[转]

    很显然,APK安装包越小越好.下面从代码,资源文件,使用策略几个方面简要介绍下: 代码 保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs. 使用proguard混淆 ...

  5. Android App安装包瘦身计划

    Android App安装包瘦身计划 Android App安装包体积优化: 理由, 指标和可以采用的方法. 本文内容归纳如下图: 为什么要安装包瘦身 安装包需要瘦身吗? 不需要吗? 安装包要瘦身的主 ...

  6. iOS ipa包瘦身,iOS8及以下text段超60MB

    前沿 很早之前写过一篇相关文章,不过博客主机上跑路了之后数据没了,凭着记忆补了下相关资料 ipa安装包瘦身 清理无用图片,图片压缩(PNG换WebP和JPG),处于某种不可抗拒的原因,导致有部分3X图 ...

  7. iOS开发——程序员必备&iOS安装包的三种格式 deb、ipa 和 pxl的解释和说明

    iOS安装包的三种格式 deb.ipa 和 pxl的解释和说明 目前 iOS 平台上常见的安装包有三种,deb.ipa 和 pxl.转自链接:http://fanlb.blogbus.com/logs ...

  8. 如何减小ios安装包大小

    以前的老文章了,搬到cnblog 更小的安装包意味着更快的下载安装速度,也往往意味着更快的加载运行速度,是优化ios应用的一个重要方面,本文主要参考<减小iOS应用程序的大小>,在实际测试 ...

  9. iOS - Bitcode App 瘦身中间码

    1.Bitcode 随着 Xcode7 的发布,Apple 提供了一项新的技术来支持 App 瘦身功能,那就是 Bitcode. 1.BitCode 是什么 Bitcode is an interme ...

  10. 在Visual Studio 2013/2015上使用C#开发Android/IOS安装包和操作步骤

    Xamarin 配置手册和离线包下载 http://pan.baidu.com/s/1eQ3qw8a 具体操作: 安装前提条件 1. 安装Visual Studio 2013,安装过程省略,我这里安装 ...

随机推荐

  1. Linux 安装Jupyter notebook 并开启远程访问

    一. Ubuntu下安装jupyter notebook 1. 使用Anaconda安装 conda install jupyter notebook 2. 使用pip安装 pip install j ...

  2. 【POJ 2279】Mr. Young’s Picture Permutations【线性DP】

    题目: 有N个学生合影,站成左端对齐的k排,每排有 \(N-1,N_2,-N_k\)个人,第一排在最后面.学生的身高互不相同,分别为\(1-N\),并且合影时要求每一排从左往右身高递减,每一列从后往前 ...

  3. 深入理解web协议(二):DNS、WebSocket

    本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/AkbAN4UZLDf841g1ZLFPBQ作者:Wu Yue 本文系统性的讲述了 DNS 协议与 ...

  4. el-date-picker 组件时间格式化方式

    1 <el-form-item label="安放龙骨时间"> 2 <el-date-picker 3 v-model="baseInfoForm.se ...

  5. C#利用折线图分析产品销售走势

    图形界面 数据 查询效果 代码 private void button1_Click(object sender, EventArgs e) { G++; DrowFont(this.comboBox ...

  6. java项目实践-tomcat实现用户登录-day17

    目录 1. 安装 2. 初识tomcat 2. 创建tomcat项目 3. 启动之前 3. java jsp 4. Request Response对象的其他属性 5. 用户登录小功能 1. 安装 t ...

  7. 深入理解Kafka核心设计及原理(六):Controller选举机制,分区副本leader选举机制,再均衡机制

    转载请注明出处:https://www.cnblogs.com/zjdxr-up/p/15026824.html 目录: 6.1.Kafka核心总控制器Controller 6.2.Controlle ...

  8. Linux系列之文件和目录权限

    前言 我们知道,root用户基本上可以在系统中做任何事.其他用户有更多的限制,并且通常被收集到组中.你把有类似需求的用户放入一个被授予相关权限的组,每个成员都继承组的权限. 让我们看一下: 查看权限( ...

  9. VS中多个源文件中只运行其中特定文件

    1.问题 有时候一个项目中创建了多个源文件,但是我只想运行其中的一个,一起运行就会出现多个main入口的问题 2.解决方式 2.1 右键要排除的文件,点击属性 2.2 从生成中排除一项中选择是即可 2 ...

  10. k8s~istio的安装与核心组件

    安装istio 在线安装:https://istio.io/latest/docs/setup/getting-started/#download 或者直接在这里下载:https://github.c ...