背景
闲鱼包大小随着用户的增多和业务的持续迭代不断增长。过去的半年时间,Android端包大小涨了43%,iOS端也涨了26%,若再不加管控,按照目前的增长速度iOS再过1年可直逼200M。
包大小对应用的下载转化率和留存率起到至关重要的作用。首先是苹果公司不支持流量下载超过200M的包,且据Google play统计,包大小每增加6M,下载转化率会下跌1%。此外也能提升用户的留存率,因为当手机内存不够用时,会优先删除占内存较大的app。
为了让每一次新需求集成时,开发能切实感受到对整体包大小的影响,让每一次包大小变动都有迹可循,需要对包大小进行科学分析。现在开发每次提交代码后,会触发打包平台自动打包,包构建成功后会自动进行包对比分析,并发回开发包增量分析报告邮件。在介绍这个包增量分析工具之前,先给大家科普下安装包的构成。
安装包构成
安卓包构成
Android安装包是以后缀名为“apk”的压缩包。闲鱼安装包解压后的主要构成如下:
AndroidManifest.xml
配置文件:包含包名、组件、权限的配置信息等。
resources.arsc
android资源索引表:包含资源名称、类型、值、ID和配置信息。
classes.dex
:是很多.class文件处理后的产物,最终可以在 Android 运行时环境执行。
/assets
:放置随包打入的文件,如text、二进制数据等。
/res
资源文件:存放图片、字符串、布局文件、xml文件等,运行打包时没有使用的文件资源不会打入包中。
/lib
:程序运行时依赖的库。
iOS包构成
iOS安装包是后缀为“ipa”的压缩包,解压后主要包含以下几部分:
签名文件:里面的CodeResources包含对bundle中的所有资源文件的签名信息。
资源文件:程序运行过程中需要的资源,比如图片、音频、视频、nib文件、配置文件等。
可执行文件:是通过编译器、连接器将我们编写的代码、静态库、动态库编译成的文件,是程序的主体。其中静态库在链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝,动态库则是链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用。
bundle文件:工程中使用的其他第三方或资源的bundle。
包分析方案
安卓包分析
apk压缩包按照资源文件类型分类,主要有:so资源(程序运行依赖的库,如接入UC浏览器内核SDK时,引入的so达到惊人的12M)、图片资源(png、webp、jpg等)、Java代码(dex文件)、xml代码这几类,此外还可横向统计flutter相关资源情况。
由于可以拿到单个文件的信息,所以我们开发了工具解析apk包中的内容,从文件类型角度分析包资源占比情况,以及将资源文件按照大小排序展示,并以图表形式直观告诉开发资源情况。
iOS静态库大小
通过分析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
0x100006CA00x000000EC[ 3] __MNN10CPUPermuteC2EPNS_7BackendEPKNS_2OpE
0x100006D8C0x0000004C[ 3] __MNN2Op15main_as_PermuteEv
0x100006DD80x00000008[ 3] __MNN10CPUPermute8onResizeERKNSt3_$body
0x100006DE00x00000580[ 3] __MNN10CPUPermute9onExecuteERKNSt3_$body
0x1000073600x00000034[ 3] __MNN38___CPUPermuteCreator__OpType_Permute__Ev
0x1000073940x00000070[ 3] __MNN10CPUPermuteD1Ev
在Symbols部分,Address是偏移地址,Size是所占内存大小,File所指的编号就是Object files中的文件编号,将Symbols中所有相同编号的Size累加起来,即可获得该编号对应可执行文件的大小了。
然后根据可执行文件的目录信息,可知该文件所属的静态库,从而计算出对应静态库代码大小。此外静态库大小在计算时需要排除对应的 dead Stripped Symbols
的大小来获得真正的二进制大小,因为这些是无用的符号,链接的时候不会加入。
iOS动态库大小
动态库通常位于安装文件根目录下的Frameworks目录中,通常是一个后缀为.framework的文件夹。
由于动态库在arm64架构下比较大,且苹果在拆包阶段会将打包好的通用的主二进制和动态库拆分成单架构的主二进制和动态库,所以只要是涉及二进制只计算arm64单架构的情况即可。
动态库大小是直接用 lipo
拆成arm64单架构大小后计算framework文件夹占磁盘大小获得。
iOS模块大小
模块大小为库大小+该模块所有资源文件占磁盘的大小,若为动态库的模块,则资源大小仅计算拷贝到主bundle的资源。分析结果如下图所示:
需求增量卡口
瘦包主要靠开发努力,要让大家在平时开发中有瘦包的意识,最好有工具能让开发在日常开发中清楚知道每个文件/模块的大小,切实感受到需求集成后对整体包大小的影响和相关文件/模块变动情况,从而促进开发进行相应的优化。
增量自动分析
通过将前面介绍的包分析能力集成到打包脚本,在每次包构建成功时,也会同步产出基础的包内容信息,再通过进一步的分析后获得包中每个文件/模块的大小情况。当代码改动触发重新打出新包后,文件/模块通过一一对比的方式,找出哪些有新增,哪些被删除,哪些内容发生变动,以及变动产生的大小,并产出对比报告邮件。通过这样的方式让开发对代码增量有一个直观感受。
那如何让包增量分析工具能在日常开发中持续稳定发挥作用呢,接下来介绍闲鱼的需求增量卡口设计。
增量卡口设计
在之前,闲鱼的包大小差异通常都在拉出集成分支,打出版本release包时才发现,经常会震惊于这个版本的包又比上一个版本要大几M,然后再紧急去寻找是什么需求集成导致的巨大增量。但这时发现包大小的问题已经非常滞后了,版本马上就要发布,这个时候即使抓到了剧增的源头,也很难在短时间内进行优化。
因此需要增加需求集成卡口,测试通过后在合入主分支之前,经过包增量确认再集成,而不是在集成后打出release包时。现在的做法如下,开发只需要提交代码,即可自动获得包增量分析报告。
其中包增量对比邮件内容,会包含与主分支最新构建、当前分支前一次构建,当前分支最初一次构建包的包大小和增量的对比结果。此外为了数据的准确性,需要开发在拉出开发分支后先构建一个基准包,并在提测和集成前合并一把主干,这样报告数据才会更准确。
最后是提测部分,开发同学发送提测邮件时需要标注本次提测包增量及图片压缩情况,若需求增量大于100K,根据超出范围情况,需要备注原因和老板确认。bug修复期间不免也会有代码改动,在测试完成后集成前,会再确认一次包增量情况再集成。
效果与展望
闲鱼需求增量卡口自6.20正式上线以来,所有客户端新需求都受到了严格的卡口,可以感受到以下明显变化:
iOS当前V6.7.40实现半年多以来首次出现了降幅。
开发过程中,开发有意识通过优化老业务代码和资源,为新需求增量挪出空间。
需求增量卡口是长效控制包大小的一个关键路径,加强开发在日常编码中的瘦包意识,让每一次需求都有迹可循。此外flutter产物更精细的分析方案和有效的瘦身方法也在持续探索中,对于存量的资源、业务代码清理等手段也在有序进行,请对小胖鱼早日瘦身拭目以待吧~
闲鱼技术团队不仅是阿里巴巴集团旗下闲置交易社区的创造者,更是移动与高并发大数据应用新技术的引导者与创新者。我们与Google Flutter/Dart小组密切合作,为社区贡献了多个高star的项目和大量PR。我们正在积极探索深度学习和视觉技术在互动、交易、社区场景的创新应用。闲鱼技术与集团中间件团队共同打造的FaaS平台每天支持数以千万级用户的高并发访问场景。
就是现在!客户端/服务端java/架构/前端/质量工程师面向社会+校园招聘,base杭州阿里巴巴西溪园区,一起做有创想空间的社区产品、做深度顶级的开源项目,一起拓展技术边界成就极致!
*投喂简历给小闲鱼→[email protected]
开源项目、峰会直击、关键洞察、深度解读
请认准闲鱼技术