背景
通过一份Google的数据,下载大小和转换率有关系[参考]。
1 | For every 6 MB increase to an APK’s size, we see a decrease in the install conversion rate of 1%. |
前提
要想优化包大小,了解包的构成是前提。对于iOS的App来说,直接下载ipa,解压开就能看到包的构成。
- 二进制代码
- 资源bundle - 独立bundle管理 .bundle格式
- Frameworks - 动态库,12.2以下可能会包含swift动态库,12.2及以上则只要依赖的动态库
- Plugins - 如果App有Extension等功能,则会有这部分
- 翻译 - 可能是在main bundle中,也可能是在独立bundle中,以strings文件呈现
- 散落在main bundle的各种其他资源(没有用独立bundle管理)
通过这里可以看出,按照类型分,包的组成大致是以下几类:
- 代码 - 二进制+Pluigns中的代码+Frameworks中的动态库
- 资源:图片/json/db/mp3等等资源
- 翻译:strings文件(其实可以算资源一种,单独列出来是因为我们的这部分比较大)
方法论
- 删除 - 删除无用
- 压缩 - 压缩留在包内的
- 转移 - 能远程加载的远程加载
这里面要考虑很多东西,比如:不是所有的资源都适合远程加载,因为可能会影响用户体验,而用户体验又可能接着影响跳出率/订单转化 等指标,所以把握不准的时候,最好先用数据说话。
具体方案
方案其实就是,将不同类型的包组成(代码/资源/翻译)和不同方案(删除/压缩/转移)的组合,我们一个一个来枚举。
由于用户只能在AppStore上看到安装大小,所以我们下面讨论的都是基于优化安装大小来说的。
代码
删除代码
通过找出工程中的无用代码来删除
对于查找无用代码的方法,个人尝试了以下两种:
反编译或者分析linkmap
主要就是找出二进制中描述的所有方法,再结合
__DATA.__objc_selrefs
段找出所有使用到的方法,两者作差集得到无用的方法(selector
)。结合
__DATA.__objc_classlist
和__DATA.__objc_classrefs
作差集找出无用的类。编写编译插件接入编译过程分析
通过编写clang的插件,在遍历AST阶段分析代码之间的调用关系。比如在A类中使用
performSelector(method)
那么A和method就有了调用关系。将所有的类信息和类的调用信息收集之后,就可以分析出调用了哪些方法,没有调用哪些方法。上述两种方法尝试后,各有优缺点:
方法1分析耗时很短,但是由于OC的语言特性,会有很多误报。方法2因为定制化程度比较高,如果我们有自己定义的router或者运行时方法,可以分析出来,精确度比方法1高,但是要全源码编译,耗时很长很长。
通过编译配置,让编译器帮我优化代码
- Dead Code Strip - YES
- Strip Link Product - YES
- LTO-incremental(需要在源码编译阶段,而不是在壳工程link阶段配置)
- Optimization Level - Os是既快又小,Oz是aggressive size optimization,对size的优化效果比Os好,同样这个配置也是源码阶段配置
- Deployment Target设置为12.2,这种方式意味着最低版本支持到12.2,需要考虑自己的App用户占比来决定,好处是12.2以上系统内置swift动态库,不会将Swift动态库打进App包内。
- 其他
- 使用flutter的工程可以考虑做下数据段和代码段剥离
- flutter中的符号信息剥离
压缩代码
代码不能压缩,此段略去。
转移代码
理论上代码是不能远程下发的,但是将native业务改成react native然后远程下发,不失为一种手段。
资源
删除资源
和代码类似,资源的删除是基于找到无用的资源。
native资源
因为图片或者其他资源文件的读取方法就那么几个,再考虑到内部封装的方法,所以直接遍历整个工程,找出所有资源文件,再从代码中用正则匹配使用到的资源,很容易能找到无用的资源。
flutter资源
开启tree shaking icon
压缩资源
- 对于图片可以使用tinypng或者ImageOptim等工具进行压缩(一劳永逸:做到打包流程中)
- 对于其他资源,都可以进行打包时压缩成7z(7z压缩率比较高),使用时再解压。
转移资源
- On Demand Resource - 还在实验中,4M的资源下载要花10s左右,如果不是很重要的资源可以尝试
- 图片等转远程url,使用时下载
翻译
删除翻译
建立使用率上报机制,定期分析无用的翻译进行删除
压缩翻译
因为用的stirngs文件,所以没有考虑过压缩。
转移资源
一个用户不可能用到所有的语种,所以可以按照不同locale的用户占比,将占比低的翻译通过启动时远程下载。
可能会影响用户体验,所以要配置好保底哪些语言,远程下发哪些语言,及时监测业务指标。
持续保持
size的优化肯定不是一次做完就不管的,所以为了长期保持size稳定,我们要建立好长期的管控机制。
分析各个模块size占比
通过linkmap分析每个模块大小,再结合对应的资源和翻译,算出每个模块的大小
为每个模块设置上限卡点
size的保持肯定不是一个人或者一个部门就能做到的,给每个模块设定上限,每次打完模块包分析size数据,当数据超标时会无法参与集成。
总结
时间有限,没法讲得很详细,以上只能算是趁自己有空做的简单记录。