包体积优化方案

发布时间 2023-10-08 14:31:33作者: Tears_fg

优势

减少包体积,让用户在更快的速度下载使用应用,提高用户下载转化率。

apk组成

Android 项目最终会编译成一个 .apk 后缀的文件,实际上它就是一个 压缩包。它内部还有很多不同类型的文件,大致分为以下四类:
  • 代码相关classes.dex,我们在项目中所编写的 java 文件,经过编译之后会生成一个 .class 文件,而这些所有的 .class 文件呢,它最终会经过 dx 工具编译生成一个 classes.dex
  • 资源相关resassets、编译后的二进制资源文件 resources.arsc 和 清单文件 等等。resassets 的不同在于 res 目录下的文件会在 .R 文件中生成对应的资源 ID,而 assets 不会自动生成对应的 ID,而是通过 AssetManager 类的接口来获取。此外,每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应的 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些 ID 和 资源文件位置对应关系 的文件。
  • so相关:lib目录下的文件。
此外,还有 META-INF,它存放了应用的 签名信息,其中主要有 3个文件,如下所示:
  • MANIFEST.MF:其中每一个资源文件都有一个对应的 SHA-256-Digest(SHA1) 签名,MANIFEST.MF 文件的 SHA256(SHA1) 经过 base64 编码的结果即为 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值。
  • CERT.SF:除了开头处定义的 SHA256(SHA1)-Digest-Manifest 值,后面几项的值是对 MANIFEST.MF 文件中的每项再次 SHA256(SHA1) 经过 base64 编码后的值。
  • CERT.RSA:其中包含了公钥、加密算法等信息。首先,对前一步生成的 CERT.SF 使用了 SHA256(SHA1)生成了数字摘要并使用了 RSA 加密,接着,利用了开发者私钥进行签名。然后,在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改

Apk分析

这里我们使用的是AndroidStudio2.2之后提供的Analyze APK。
Analyze APK具有如下优势:
  • 可以直观地查看到 APK 的组成,比如大小、占比等等。
  • 查看 dex 文件的组成。
  • 对不同的 APK 进行对比分析。
我们将需要优化的包拖到AS中,AS会自动使用Analy APK打开,这样就可以APK文件的绝对路径及各组成文件的百分比。
根据APK结构可以分享一个应用的资源主要区分为三种,项目代码、资源文件、支持对应CPU架构的so文件,因此我们的包体积优化也是围绕这三种资源做优化。

一、代码优化

1.ProGuard混淆

混淆的形式:

  • 改变代码中的元素,比如:类、函数、变量的名字变成无意义的字符。增加反编译阅读难度。
  • 重写代码中的部分逻辑,将其变成功能等价但难以理解的形式。比如:循环的指令、结构体。
  • 打乱代码的格式。比如:增加或者删除一些空格。

混淆的作用:

  • 瘦身:检测到无用的资源、类、方法,将其移除,并且能对代码进行深度优化。
  • 安全:将代码中的元素变成简短,无意义的字符,增加反编译阅读难度,使代码更为安全。

混淆的流程:

混淆的流程分为四个步骤:
压缩:减少应用体积,移除未被使用的类和成员,在优化后会再次执行。
优化:在字节码级别执行优化,让应用运行更快。
混淆:增大反编译难度,将类和成员随机命名。
预校验:在 Java 平台上对处理后的代码进行预检,确保加载的 class文件是可执行的。

开启代码混淆:

buildTypes {
    debug{
        debuggable true
        zipAlignEnabled true
        minifyEnabled false
        signingConfig signingConfigs.release
        resValue("bool", "config_center_is_debug_mode", "true")
    }

    release {
        zipAlignEnabled true//开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗
        minifyEnabled true  //开启混淆
        shrinkResources true   //打包移除未使用资源
        multiDexKeepProguard file('multidex-config.pro')
        //混淆文件的位置,它默认打开了优化开关。并且,我们可在配置混淆文件将android.util.Log置为无效代码,
        // 以去除apk中打印日志的代码。
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'//混淆规则
        signingConfig signingConfigs.release
        resValue("bool", "config_center_is_debug_mode", "false")
    }
}

使用注意:

debug环境不要混淆,增加打包编译速度

实战

将代码进行混淆后,包分析如下
缩减了34%
打包后会在release目录增加混淆后的包和mapping.txt文件,mapping.txt是混淆规则,当在线上发生崩溃日志使,可能通过mapping.txt文件反推回原始代码。

2.三方库优化

  • 相同的三方库保留一个
  • 优先选择体积小的三方库
  • 当三方库有多个功能库时,尽量最小化依赖需要的依赖库

二、so文件优化

  • armeabi-v7a:第7代及以上的 ARM 处理器,2011年大部分设备使用
  • arm64-v8a:第8代、64位 ARM 处理器,极少数设备,如三星 Galaxy S6
  • armeabi:第5、6代 ARM 处理器,早期手机
  • x86:平板、模拟器
  • x86-64:64位的平板
目前Android主流设备使用的CPU架构是32位的armeabi-v7a和64位的arm64-v8a,这代表了目前ARM处理器的主流水平。x86架构在Android上的使用较少,主要应用在一些平板电脑上

实战

保留主流的架构:
 
包大小缩减了12%

三、资源优化

1.压缩图片资源

使用免费的压缩文件将图片做压缩替换

2.图片资源格式转换

要做好图片体积优化,我们需要了解各种图片格式的优缺点,针对不同业务使用不同的图片格式。
格式
描述
优点
缺点
适用场景
jpg
进行了有损压缩,去掉了图片质量为前提,尽可能压缩大小
适合存储照片
没有alpha,保存图片会自动保存为白色
对透明通道没有要求的高保真大图要存储到磁盘的图片,优先使用 jpg
png
无损压缩,图片会比jpg大
支持alpha通道,显示效果比较好
-
一些普通图标切图的话就使用 png
webp
支持有损和无损压缩,相同质量图片webp体积更新
1、无损压缩情况下,相同质量的 webp 图片,文件大小比 png 小 26% 2、有损压缩情况下,具有相同精度的 webp 图片,文件大小比 jpg 小 25%-34%
3、webp 格式支持 alpha 通道,一个无损压缩的 webp 图片要支持透明度只需要 22% 的额外文件大小
不能完全兼容所有 平台
有用到网络传输且图片比较大的场景,比如和服务器通信获取网络图片,优先使用 webp,其次是 png
svg
svg 是无损的矢量图,是矢量图中的一种,它的特点是 使用 xml 描述图片
不会失真,体积小
渲染速度慢
不支持投影、模糊、使用场景有限
图片体积小要求不高的纯色小图标可以使用 svg
这里将项目中特别大的png转换为了webp

3.保留少量的图片目录

保留现xxhdpi和xxxhdpi

3.清除无用资源

使用androidstudio自带的Lint检查工具检测
实际测试发现,lint检测存在一些问题,可能会移除一些正在使用的资源文件,如果存在动态获取资源id的方式,那么这个资源也会认为没有使用,并且在我们上面进行混淆的时候,实际已经做过无用资源的移除了。

4.去除多语言

在app的build.gradle中的defaultConfig中设置
resConfigs ‘zh-rCN’
当前项目只有中文语言,这里去除的是引用的第三方库的多语言。

5.SVGA动画文件优化

当前项目中送礼物SVGA动画资源耗费了约20M,考虑和后端沟通通过后台下载方式优化。

实战

缩减了23%,由此可见使用webp能大大缩小包的体积。

四、其他优化

除以上外,我们还可以做的优化有AndResGuard 资源混淆。
AndResGuard 是一个资源压缩的工具,和 ProGuard 混淆代码类似,但只作用于资源文件,它会将资源路径变短例如 res/drawable/wechat 变成 r/d/a,以及重命名资源文件例如 wechat.png 变成 a.png,使用的 7zip 压缩方式能一定程度缩减包体积。
但是考虑以下场景,看项目是否使用。
  1. 动态换肤:动态换肤是通过代码查找资源路径动态替换的,因为资源路径会被 AndResGuard 混淆会导致换肤失败
  2. 项目中有大量用到 ConstraintLayout 的 Group:Group 使用 constraint_referenced_ids 属性将多个控件作为一个整体引用,使用了 AndResGuard 要引用控件 id 生效,引用的控件 id 都要加到白名单,否则操作 Group 控制控件会失效
 

总结

包体积优化第一版本优化完毕,由原始的181.2M缩减到了75M,包大小减少了58%,其中主要是从代码、so库、资源三个方面入手进行优化,在实际业务场景中,需要进行apk分析适合自己的业务场景。
 

参考链接:

  1. Android 性能优化系列:包体积优化
  2. 深入探索 Android 包体积优化