作者:闲鱼技术-金喏
1.背景
不知大家是否注意到,闲鱼的包大小在随着服务用户的增多和新业务的持续迭代不断增长。在过去的半年时间,Android端包大小涨了43%,iOS端也涨了26%,若再不加管控,按照目前的增长速度再过1年可直逼200M。
包大小对应用的下载转化率和留存率起到至关重要的作用,瘦包作为闲鱼技术今年的重点项目,意义非同凡响。
对于闲鱼来说,瘦包能提升应用的下载转化率。首先是苹果公司不支持流量下载超过200M的包,包越大下载时间越长失败率越高,且据Google play统计,包大小每增加6M,下载转化率会下跌1%。此外也能提升用户的留存率,因为当用户手机内存不够用时,肯定是优先删除占内存比较大的app。
对开发来说,定期清理废弃代码、不用的SDK和整理重复的资源,有助于提高代码的健康度和架构的合理性。
但随着每一次客户端发布,业务导致的增量使包大小默默攀升。业务需求是要做的,包大小也是要控的,为了让每一次新需求集成时,开发们能切实感受到对整体包大小的影响,让每一次包大小变动都有迹可循,需要对包大小进行科学分析。
现在开发每次提交代码后,会触发打包平台自动打包,包构建成功后会自动进行包对比分析,并发回开发包增量分析报告邮件。在介绍这个包增量分析工具之前,先给大家科普下安装包的构成。
2.安装包构成
2.1 Android包构成
Android安装包是以后缀名为“apk”的压缩包。闲鱼安装包解压后的主要构成如下:
AndroidManifest.xml
配置文件:包含包名、组件、权限的配置信息等。
resources.arsc
android资源索引表:包含资源名称、类型、值、ID和配置信息。
classes.dex
:是很多.class文件处理后的产物,最终可以在 Android 运行时环境执行。
/assets
:放置随包打入的文件,如text、二进制数据等。
/res
资源文件:存放图片、字符串、布局文件、xml文件等,运行打包时没有使用的文件资源不会打入包中。
/lib
:程序运行时依赖的库。
2.2 iOS包构成
iOS安装包是后缀为“ipa”的压缩包,解压后主要包含以下几部分:
签名文件:里面的CodeResources包含对bundle中的所有资源文件的签名信息。
资源文件:程序运行过程中需要的资源,比如图片、音频、视频、nib文件、配置文件等。
可执行文件:是通过编译器、连接器将我们编写的代码、静态库、动态库编译成的文件,是程序的主体。其中静态库在链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝,动态库则是链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用。
bundle文件:工程中使用的其他第三方或资源的bundle。
3.包分析方案
3.1 Android包分析
apk压缩包按照资源文件类型分类,主要有:so资源(程序运行依赖的库,如接入UC浏览器内核SDK时,引入的so达到惊人的12M)、图片资源(png、webp、jpg等)、Java代码(dex文件)、xml代码这几类,此外还可横向统计flutter相关资源情况。
由于可以拿到单个文件的信息,所以我们开发了工具解析apk包中的内容,从文件类型角度分析包资源占比情况,以及将资源文件按照大小排序展示,并以图表形式直观告诉开发资源情况。
3.2 iOS包分析
我们现在是从静态库、动态库、资源维度来分析包内容和增量。
3.2.1 静态库大小
通过分析link map文件获得静态库大小,网上有很多link map的解析工具。核心是解析link map文件中的Object files和Symbols中的数据。
#Object files:
[ 0] linker synthesized
[ 1] /Users/xy/build/workspace/Pods/MNN/MNN.framework/MNN(MNNReluInt8-24695ca527c0186b07.o)
[ 2] /Users/xy/build/workspace/Pods/MNN/MNN.framework/MNN(MNNGemmInt16to32_4x4_Unit-416f183fdf349b160b82.o)
[ 3] /Users/xy/build/workspace/Pods/MNN/MNN.framework/MNN(CPUPermute.o)
每一行代表对应可执行文件的编号,如CPUPermute.o文件编号是3。
#Symbols:
#Address Size File Name
0x100006CA0 0x000000EC [ 3] __MNN10CPUPermuteC2EPNS_7BackendEPKNS_2OpE
0x100006D8C 0x0000004C [ 3] __MNN2Op15main_as_PermuteEv
0x100006DD8 0x00000008 [ 3] __MNN10CPUPermute8onResizeERKNSt3_$body
0x100006DE0 0x00000580 [ 3] __MNN10CPUPermute9onExecuteERKNSt3_$body
0x100007360 0x00000034 [ 3] __MNN38___CPUPermuteCreator__OpType_Permute__Ev
0x100007394 0x00000070 [ 3] __MNN10CPUPermuteD1Ev
在Symbols部分,Address是偏移地址,Size是所占内存大小,File所指的编号就是Object files中的文件编号,将Symbols中所有相同编号的Size累加起来,即可获得该编号对应可执行文件的大小了。
然后根据可执行文件的目录信息,可知该文件所属的静态库,从而计算出对应静态库代码大小。此外静态库大小在计算时需要排除对应的dead Stripped Symbols
的大小来获得真正的二进制大小,因为这些是无用的符号,链接的时候不会加入。
3.2.2 动态库大小
动态库通常位于安装文件根目录下的Frameworks目录中,通常是一个后缀为.framework的文件夹。
由于动态库在arm64架构下比较大,且苹果在拆包阶段会将打包好的通用的主二进制和动态库拆分成单架构的主二进制和动态库,所以只要是涉及二进制只计算arm64单架构的情况即可。
动态库大小是直接用lipo
拆成arm64单架构大小后计算framework文件夹占磁盘大小获得。
3.2.3 模块大小
模块大小为库大小+该模块所有资源文件占磁盘的大小,若为动态库的模块,则资源大小仅计算拷贝到主bundle的资源。分析结果如下图所示:
3.需求增量卡口
瘦包主要靠开发努力,要让大家在平时开发中有瘦包的意识,最好有工具能让开发在日常开发中清楚知道每个文件/模块的大小,切实感受到需求集成后对整体包大小的影响和相关文件/模块变动情况,从而促进开发进行相应的优化。
3.1 增量自动分析
通过将前面介绍的包分析能力集成到打包脚本,在每次包构建成功时,也会同步产出基础的包内容信息,再通过进一步的分析后获得包中每个文件/模块的大小情况。当代码改动触发重新打出新包后,文件/模块通过一一对比的方式,找出哪些有新增,哪些被删除,哪些内容发生变动,以及变动产生的大小,并产出对比报告邮件。通过这样的方式让开发对代码增量有一个直观感受。
那如何让包增量分析工具能在日常开发中持续稳定发挥作用呢,接下来介绍闲鱼的需求增量卡口设计。
3.2 增量卡口设计
在之前,闲鱼的包大小差异通常都在拉出集成分支,打出版本release包时才发现,经常会震惊于这个版本的包又比上一个版本要大几M,然后再紧急去寻找是什么需求集成导致的巨大增量。但这时发现包大小的问题已经非常滞后了,版本马上就要发布,这个时候即使抓到了剧增的源头,也很难在短时间内进行优化。
因此需要增加需求集成卡口,测试通过后在合入主分支之前,经过包增量确认再集成,而不是在集成后打出release包时。现在的做法如下,开发只需要提交代码,即可自动获得包增量分析报告。
其中包增量对比邮件内容,会包含与主分支最新构建、当前分支前一次构建,当前分支最初一次构建包的包大小和增量的对比结果。此外为了数据的准确性,需要开发在拉出开发分支后先构建一个基准包,并在提测和集成前合并一把主干,这样报告数据才会更准确。
最后是提测部分,开发同学发送提测邮件时需要标注本次提测包增量及图片压缩情况,若需求增量大于100K,根据超出范围情况,需要备注原因和老板确认。bug修复期间不免也会有代码改动,在测试完成后集成前,会再确认一次包增量情况再集成。
4.效果与展望
闲鱼需求增量卡6.20正式上线至今半月,7个客户端新需求都收到了严格的卡口洗礼。以iOS为例,这半个月主干分支包大小不增反降0.5M,在开发过程中,开发也开始有意识通过优化老业务代码和资源,为新需求增量挪出空间。
需求增量卡口只是长效控制包大小的一个手段,加强开发在日常编码中的瘦包意识,让每一次需求都有迹可循,此外对于存量的资源、业务代码清理等手段也在有序进行,flutter产物更精细的分析方案和有效的瘦身方法也在持续探索中,期待小胖鱼早日瘦身成功吧~