第十四章:包体积优化——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 知识体系总览
下面这张图,我把包体积优化的核心路径画出来了。你照着这个框架走,基本不会漏掉关键点。
14.6 总结一下
包体积优化不是一锤子买卖,而是一个持续的过程。每次发版前,我都习惯跑一遍AAPT2看看体积变化。如果发现某次体积异常增长,立刻定位原因。
核心就四点:
- 分析先行——用AAPT2或APK Analyzer搞清楚体积分布
- 资源混淆——AndResGuard,简单有效,15%起步
- 代码混淆——R8走起,Dex体积砍掉30%~40%
- 动态库拆分——只留arm64-v8a,so体积直接减半
这四招组合下来,APK瘦身40%以上不是问题。你想想看,用户下载一个30MB的App和下载一个60MB的App,心理门槛完全不一样。瘦身带来的转化率提升,比投多少广告都实在。
最后说一句:包体积优化做到极致,是对用户的尊重。别让用户因为“存储空间不足”而卸载你的App。
公众号:蓝海资料掘金营,微信deep3321