第十四章:包体积优化——APK瘦身实战

包体积优化,说白了就是给APK“减肥”。

我记得刚入行那会儿,公司有个项目APK体积直奔80MB。用户下载转化率低得可怜,尤其是二三线城市,WiFi覆盖率不高,很多人看到80MB直接就放弃了。后来我花了整整两周做瘦身,硬生生砍到28MB,下载转化率提升了将近40%。

嗯,今天我就把这几招核心手段拆开来讲。你跟着走一遍,APK瘦身基本就稳了。

14.1 APK结构分析——先搞清楚“胖”在哪

动手之前,得先知道APK里到底装了啥。我个人习惯用AAPT2来分析,这是Android官方提供的打包工具,比老版AAPT强不少。

14.1.1 用AAPT2查看APK内容

# 列出APK中的所有文件
aapt2 dump resources your_app.apk

# 查看APK的详细结构
aapt2 dump configurations your_app.apk

# 查看APK的包名、版本、权限等
aapt2 dump badging your_app.apk

你跑一下命令,会看到类似这样的输出:

Package name: com.example.myapp
versionCode: 1001
versionName: 1.0.1
sdkVersion: 21
targetSdkVersion: 33

resources.arsc
classes.dex
classes2.dex
classes3.dex
res/
lib/
META-INF/
AndroidManifest.xml

看到没?classes.dex、res、lib这三块,基本就是APK体积的“三大元凶”。

核心结论:APK体积的80%以上通常集中在Dex文件、资源文件、Native库这三块。优化就盯着它们打。

14.1.2 可视化分析工具

除了命令行,我推荐你用Android Studio自带的APK Analyzer。直接拖APK进去,每个文件的大小、占比一目了然。

我在项目中遇到过一件事:有个同事说“我代码没写多少,怎么APK这么大?”我用APK Analyzer一查,好家伙,assets目录下塞了三个没用的字体文件,每个8MB,加起来24MB。删掉之后,APK直接瘦了30%。

小技巧:APK Analyzer还能对比两个APK版本的差异,升级后体积涨了多少、涨在哪,一目了然。

14.2 资源混淆——AndResGuard实战

资源文件(图片、布局、字符串等)在APK里占大头。但有个问题:资源文件名通常很长,比如activity_main_dark_mode_landscape.xml,这名字本身就要占不少字节。

AndResGuard就是干这个的——把资源路径缩短。比如把res/drawable/ic_launcher_background.png变成r/d/a.png

14.2.1 集成AndResGuard

// 在项目根目录的build.gradle中
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.21'
    }
}

// 在app模块的build.gradle中
apply plugin: 'com.tencent.mm.AndResGuard'

andResGuard {
    mappingFile = null
    use7zip = true
    useSign = true
    keepRoot = false
    // 白名单,这些资源不混淆
    whiteList = [
        "R.mipmap.ic_launcher",
        "R.drawable.splash_bg"
    ]
    compressFilePattern = [
        "*.png",
        "*.jpg",
        "*.jpeg",
        "*.gif"
    ]
    sevenzip {
        artifact = 'com.tencent.mm:SevenZip:1.2.21'
    }
}

注意:有些资源不能混淆,比如通过反射获取的资源、第三方SDK硬编码的资源。一定要加白名单,否则运行时直接崩溃。我曾经因为漏加一个白名单,导致线上用户打开页面就闪退,紧急发版才修复。

14.2.2 效果评估

优化项 优化前 优化后 节省
资源文件总大小 12.5 MB 8.3 MB 33.6%
resources.arsc 1.2 MB 0.8 MB 33.3%
APK总大小 28.6 MB 24.2 MB 15.4%

你看,光资源混淆这一项,APK就能瘦15%左右。而且对用户完全无感,运行时没有任何性能损耗。

14.3 代码混淆——ProGuard与R8

代码混淆不只是为了安全,它还能大幅缩减Dex体积。说白了,就是把com.example.myapp.utils.StringUtils变成a.b.c,同时把没用到的代码统统删掉。

14.3.1 R8 vs ProGuard

从Android Gradle Plugin 3.4开始,Google默认用R8替代ProGuard。R8更快、优化更激进。我个人建议直接用R8,别折腾ProGuard了。

// 在gradle.properties中启用R8
android.enableR8=true
android.enableR8.libraries=true

// 在build.gradle中配置
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

14.3.2 混淆规则示例

# 保留实体类(Gson/Json解析需要)
-keep class com.example.myapp.model.** { *; }

# 保留反射调用的类
-keep class com.example.myapp.plugin.** { *; }

# 保留JNI方法
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留枚举
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 移除日志(Release包)
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
}

避坑指南:我曾经在混淆配置里漏掉了某个第三方SDK的keep规则,结果上线后用户反馈支付页面打不开。排查了半天,发现是SDK的某个回调类被混淆删掉了。从那以后,我养成了一个习惯:每次发布前,先用混淆后的APK跑一遍全功能测试。

14.3.3 混淆效果

一个中等规模的项目,开启R8混淆后:

  • Dex文件数量:从5个减少到3个
  • Dex总大小:从18MB降到11MB,节省约39%
  • 方法数:从8万降到4.5万,直接避开64K方法数限制

14.4 动态库与ABI拆分

Native库(.so文件)是APK体积的另一个大头。一个完整的OpenCV库可能就有20MB。但问题是,你的用户真的需要所有CPU架构的so吗?

14.4.1 ABI是什么

Android设备有几种CPU架构:

ABI 对应架构 市场占比(2024年)
armeabi-v7a 32位ARM 约15%
arm64-v8a 64位ARM 约80%
x86 / x86_64 Intel架构 约5%(模拟器为主)

你看,arm64-v8a占了80%以上。如果你把armeabi-v7a和x86的so都打进APK,那这20%的体积就是白费的。

14.4.2 ABI拆分方案

我推荐两种做法:

方案一:只保留arm64-v8a

android {
    defaultConfig {
        ndk {
            abiFilters "arm64-v8a"
        }
    }
}

适合绝大多数App。32位设备用户极少,而且Google Play从2021年8月起要求新应用必须支持64位。

方案二:按ABI打多个APK

android {
    splits {
        abi {
            enable true
            reset()
            include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
            universalApk false
        }
    }
}

这样会生成多个APK,每个只包含对应架构的so。用户下载时,应用商店会自动匹配最合适的版本。

实战效果:我之前维护的一个直播App,so文件占了35MB。通过ABI拆分,只保留arm64-v8a,so体积降到18MB。再加上资源混淆和代码混淆,APK从72MB瘦到了38MB,几乎砍了一半。

14.4.3 动态库压缩

如果so实在太大,还可以考虑用XZ压缩。在打包时压缩so,运行时解压到私有目录。不过要注意:这会增加启动耗时,属于“用时间换空间”的策略。

// 在build.gradle中配置
android {
    packagingOptions {
        // 对so文件使用XZ压缩
        jniLibs {
            useLegacyPackaging true
        }
    }
}

注意:XZ压缩so会额外增加50-100ms的解压时间。如果你的App对启动速度极其敏感(比如闪屏广告倒计时那种),慎用。

14.5 知识体系总览

下面这张图,我把包体积优化的核心路径画出来了。你照着这个框架走,基本不会漏掉关键点。

APK包体积优化核心路径 1. 分析APK结构 AAPT2 / APK Analyzer 2. 资源混淆 AndResGuard 3. 代码混淆 ProGuard / R8 4. 动态库拆分 ABI Filters / Splits APK体积减少 40%~60% 下载转化率提升 20%~40% 四步走:分析 → 资源 → 代码 → 动态库,步步为营

14.6 总结一下

包体积优化不是一锤子买卖,而是一个持续的过程。每次发版前,我都习惯跑一遍AAPT2看看体积变化。如果发现某次体积异常增长,立刻定位原因。

核心就四点:

  • 分析先行——用AAPT2或APK Analyzer搞清楚体积分布
  • 资源混淆——AndResGuard,简单有效,15%起步
  • 代码混淆——R8走起,Dex体积砍掉30%~40%
  • 动态库拆分——只留arm64-v8a,so体积直接减半

这四招组合下来,APK瘦身40%以上不是问题。你想想看,用户下载一个30MB的App和下载一个60MB的App,心理门槛完全不一样。瘦身带来的转化率提升,比投多少广告都实在。

最后说一句:包体积优化做到极致,是对用户的尊重。别让用户因为“存储空间不足”而卸载你的App。


公众号:蓝海资料掘金营,微信deep3321