Android-Flutter混合开发包体积优化

最近在忙Android包体积优化的工作,将包体积从大于87MB降到了不到65MB,降幅超过25%,做到了全行业竞品包体积最小。总结下用到的工具和方法,希望对多团队、业务复杂的大型项目APK瘦身有所帮助。项目中包含了公司基础架构大仓和Flutter混合开发。

包体积各项内容占比分析

在具体进行着手包体积优化之前,首先要了解包体积的构成,做到心中有数,有的放矢。这里主要借助AndroidStudio的APK Analyzer。使用方法也很简单直接将APK拖到AndroidStudio的窗口中,过一会儿AS就能自动完成包体积的占比排序,示例图如下:


image.png

可以看到占比靠前的就是so文件、资源文件和dex文件。

多团队项目一旦大了之后,给到最后的业务开发同学一种很强的迷茫感(很不幸,我也是其中之一)。这么多引用的库是做什么的?

大型的公司项目中往往有很多基础库,甚至会混进去很多当前业务用不到的库(有基础架构的大公司业务端开发同学应该明白这里面的痛苦)。很多时候根本不清楚这些库的功能,也不清楚这些库包含什么so。造成尝试优化引用类库的时候畏手畏脚,甚至删了半天库apk的体积没见缩小多少,甚至引起线上某个角落里的功能类找不到崩溃。这时候就需要识别哪个库中包含哪个so、每个库的dex方法占比是多少和资源文件占用排名。

下面我们就来逐步通过相应的工具和方法解答上面这些疑问,定位问题并尝试优化。

分析so文件

首先我们看下体积占比最高的so文件。


image.png

这里面有很多so,里面有一些是业务开发同学可以直接搞明白的,比如上图中的libapp.so和libflutter.so是因为项目用到了flutter。其他的可就没那么容易了,错综复杂的基架和各路业务方用的各种奇奇怪怪的名称。

接下来就要搞清楚每个so是来自哪个业务,即来自哪个引用?这里推荐使用AnalyzeSoPlugin:https://github.com/mainlxl/AnalyzeSoPlugin(这个库使用时需要做一些修改,直接引用会报错),先搞清楚每个so来自哪个引用库。再逐一和基架、业务方确认是否在当前的apk业务中有所使用。确认无用的就可以在gradle 里面打包的时候exclude掉。本项目通过这个方法排除了两个包含so的库,体积下降超过3MB。

Dex文件分析

在面对dex文件的时候,同样会产生so文件的疑惑,到底是哪些库占用了这么多的dex体积?每个库的占比是多少?该怎么高效的去优化用不到但是体积占比又很大的库?这时候就要用到android-classyshark:https://github.com/google/android-classyshark。建议使用debug包来分析,这样避免混淆丢失识别包名信息,反正要的是占比。下面是一个典型的分析结果图:

image.png

可以看到排名靠前的库占了快1/3,逐层点进去就能看到更详细的包名路径和占比。这里就有了很好的抓手,优先对占比高的库进行业务使用确认。用不到的在gradle里面打包的时候exclude掉。本项目通过这个方法排除了6MB左右的库。

资源文件分析

这个就更加复杂了,单纯的Android项目还好,尤其是混合开发(flutter),各种各样的资源让人感到非常棘手。逐一分析解决之。

Android原生项目

首先使用AS的Refactor -> RemovedUnusedResource帮助移除用不到的资源,这样一方面降低了体积,另一方面降低了后续资源分析的工作量。
接下来就需要对res里面的图片资源进行详细的分析。

资源删减

对于有多个分辨率的图片资源只保留xxhdpi的资源即可,其他的删除掉。
发现项目中遗留的历史资源图片或者字体,删除掉。

图片、字体资源转在线

这一步收效很大,很多图片、字体资源可以不用放在本地,而是在使用的时候去网络加载。后续通过带有缓存功能的图片加载库加载对应的链接即可。我在做这步的时候特意开发了IntelliJ和VSCode的插件,可以将项目中的图片传到后端转换成链接,并生成模版代码,直接拷贝到原来的本地资源文件位置。


image.png

image.png

这样就能便捷的把本地资源转换成在线链接。当然优先的还是体积较大的资源,可以通过AS的APK Analyzer查看:


image.png

图片资源转webp

对于删减、上传后端完剩下来的图片(比如某些提示引导图、返回箭头等不方便用网络加载的资源),可以使用AndroidStudio自带的convert to webp功能,将png等资源转成webp。这里可能出现转完webp后体积增大的情况,AS自带的工具可以帮助我们避免这个问题。Android原生项目转完不需要做什么事,flutter项目需要注意,转完后要到工程中搜索用到的地方,把原先的引用资源文件后缀名改掉。

图片资源后续 --- CI检查

如果只是做到了上面也只是控制住了现有的图片资源,后续如果其他开发同学又上传了很多图片资源,包体积又不可避免的要膨胀了。这时候需要配合CI在提交MR的时候对包含图片等资源的MR做额外的approve机制(当然approver就是包体积okr的持有者)。这里我用了gitlab的CODEOWNERS机制,细节实现参见https://docs.gitlab.com/user/project/codeowners/即可。
这样后续图片资源引起包体积增长也能处于控制之中。

通过上述措施,包体积下降10MB左右。

引用库删减

这个就需要具体到业务了。拿我现在的项目举例,在flutter的工程中用到了flutter_html库,但是这个库中会包含一个数学公式的计算渲染库,这在应用中是用不到的,所以删减掉这里面数学公式计算渲染的部分。这样就可以移除这个数学公式计算渲染库,一方面缩减了代码,另一方面这个库里还包括的很多字体文件也一并移除了。

其他的三方库就不一一介绍了,要对项目中的三方库功能和应用熟悉才能在这里做出有成果的删减。

通过上述措施,包体积下降1~2MB左右。

so文件压缩和网络下发

即使是在第一步排除用不到的so之后,so文件的总大小依然占据第一位,这时候就需要对已有的so文件进行压缩或网络下发。但是由于部分so需要在初始化的时候就要用到,网络下发就不再适用,并且压缩so解压缩也会影响App冷启动时长(碰巧如果你也是冷启动时长okr的owner的话,哎,一根筋变两头堵了!)。这时候就需要对so在App冷启动到正常使用的加载顺序要心中有数,避免上面提到的尴尬问题。接着往下分析。

如何确定so的加载顺序?

这里可以hook系统的加载so的方法,在hook的方法里打印so的名称。或者直接使用AS Debug app方式启动App,在System.load()方法打上断点,来记录so文件在冷启动时的加载顺序。

确定保留、压缩还是下发so

主要用的库是Android-So-Handler:https://github.com/mainlxl/Android-So-Handler(需要对其进行部分修改和定制)。
核心原则就是冷启动要用的就保留so,后续业务常用的就压缩,不常用的就下发。由于前面一步已经确认了so的加载顺序,在做这一步的时候就可以做到心中有数。
当然也可以结合自研工具,采集一下线上的加载顺序。这里不再赘述。网络加载一定要慎重,在实际应用中,我就没有采用这个方式,防止so下载失败导致业务崩溃(因为崩溃率也是我负责的...)。

通过以上措施,apk体积得以缩小2~3MB左右。

结论

综上,通过这些工具和方法,最终包体积下降超过20MB,降幅超过25%。在同业竞品做到了最小,并且还存在部分优化的空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容