优雅地缩减APK体积

January 3, 2020

一、为何优化

  • 安装包体积越小,则用户抗拒下载心理越弱,可提高确认下载率;
  • 小安装包让用户下载时更主动允许流量下载,而无需延迟到Wifi;
  • 从用户体验层面来说,小体积安装包减少下载时间,同时减少安装耗时;
  • 安装包大小与安装后占用存储空间成正相关;
  • 推广按照流量收费,安装包体积越小,推广成本越低;
  • 如果交付产物是SDK,甚至会影响宿主安装包体积;

二、原因

  • 手抖把同一张图片同时放入 xhdpixxhdpi (同事犯过这错);
  • 资源图片没有压缩;不需要透明通道的 png 没有保存为 jpg
  • 存放图片实际尺寸过大;
  • 相同字符串使用不同资源名称造成重复声明;
  • 废弃、冗余代码没有及时清理,依然被其他代码引用令 Proguard 无法自动裁剪;

三、基础方案

  • Android Lint 检查开发资源是否重复;
  • 构建安装包时用脚本压缩图片资源;
  • 把所有 jpgpng 图片资源转换成 webp 或矢量资源;
  • 启用 android.enableR8.fullMode=true
  • 启用 shrinkResources truezipAlignEnabled true
  • 使用腾讯 AndResGuard 工具压缩安装包的资源名称;

四、主流格式

4.1 PNG

是否需要使用 PNG 取决于应用场景的图片是否需要透明。透明通道的图片需使用 PNG (或 WebP),这种情况只能对图片进行有损压缩减少体积。如果图片不需要透明通道但使用了 PNG 格式,应先把 PNG 转换为 JPG,再对 JPG 进行有损压缩。

4.2 JPEG

JPG 直接进行压缩或考虑利用 WebP 算法优势进一步减少体积。

4.3 WebP

需要注意 AndroidWebP 支持有版本限制,和 PNG 一样支持透明通道。

WebP的有损压缩算法是基于VP8视频格式的帧内编码[17],并以RIFF作为容器格式。[2] 因此,它是一个具有八位色彩深度和以1:2的比例进行色度子采样亮度-色度模型YCbCr 4:2:0)的基于块的转换方案。[18] 不含内容的情况下,RIFF容器要求只需20字节的开销,依然能保存额外的 元数据(metadata)。[2] WebP图像的边长限制为16383像素。[5]

五、其他方案

  • 使用原生代码代替文件配置,如这些文件:drawableanimstringcolorlayout
  • 图片资源存放在云端,运行时通过图片框架加载;
  • 手写组件AnkoAndroid JetPack 等代码实现UI布局;
  • 手动压缩 rawassets 文件夹的图片、音频、JS文件,这些文件构建时默认不会压缩;
  • 减少类声明、内部类、抽象接口,或更进一步说就是减少代码量;
  • 使用频率低的功能在使用时下载插件再启动;
  • 原生界面和 WebView 混合开发,功能转移到线上;
  • 用自定义 ViewCanvas 绘制替换矢量视图;

对 Android 来说,还有比较极端的技术方案:只留下一个尺寸的图片资源,例如:drawable-xxxhdpi。只保留一个尺寸可有效节省空间,但低端手机需要读取更大的图片资源,经过缩放才到合适的展示尺寸。对于需要出海的应用来说,可以按照屏幕尺寸个性化派发安装包。

印象里国内有不少大厂都用这种方法,我个人也推荐这种方法。为了避免每次读取文件都用原图,要用 Glide 等图片加载框架的磁盘策略,缓存缩放后的资源。

六、效果

对工程图片进行压缩、部分非重要图标只保留一份资源的处理,结果如下:

package_size

后期进一步把几张 PNG 海报转换为 JPG,体积最终减少到19.6MB,累计压缩20%体积。

七、参考链接