上一篇讲了屏幕适配,切到包体积问题。APK 每涨 1MB,用户下载转化率掉 1%。这个数字看着小,乘以亿级 DAU 就是钱。这一篇把所有能瘦身的手段系统过一遍。
APK 的组成结构
分析 APK 第一步是看里面有什么:
./gradlew app:assembleRelease
ls -lh app/build/outputs/apk/release/
# 用 Android Studio 的 APK Analyzer
# 或者反编译
java -jar apktool_2.9.3.jar d app-release.apk -o apk_extracted
典型 APK 的体积分布:
第一步:APK Analyzer 看家底
Android Studio 的 APK Analyzer 是必学工具:
APK Analyzer 能看出:
1. 每个文件的体积、占比
2. dex 里每个 package、class、method 的体积
3. 资源引用的次数(多少代码引用、是否重复资源)
4. R8 删除类的依据
常见分析结论:
"呦,我有个帮助类写了 5KB,没人用" → 删除
"哎,这图片 1024x1024,我用在了 100dp 区域" → 缩小
"这 5 个第三方库,每个 200KB" → 真需要这么多吗?
第二步:R8/ProGuard 优化
// app/build.gradle.kts
android {
buildTypes {
release {
isMinifyEnabled = true // R8 删代码
isShrinkResources = true // R8 删无用资源
}
}
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
写好 -keep 规则,避免反射类被删:
# proguard-rules.pro
-keep class com.example.model.** { *; }
-keep class com.example.api.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
R8 全模式优化比 ProGuard 强 30%+,但默认配置可能误删:
注解(用
@Keep显式标记)JNI 调用的方法(
-keepclasseswithmembernames)反射调用(
-keep class X.Y)
开启 R8 的更多优化:
buildTypes {
release {
// R8 fullMode
// 实际 Android Gradle 8.0 默认就是 fullMode
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
测试方法不能只看构建成功,要跑全量回归:
./gradlew :app:assembleRelease
./gradlew :app:connectedAndroidTest
第三步:图片资源优化
1. WebP 替代 PNG
PNG 改 WebP 平均省 25-35%:
# 单文件
cwebp -q 80 input.png -o output.webp
# 批量
for f in *.png; do cwebp -q 80 "$f" -o "${f%.png}.webp"; done
Android Studio 一键转:
选中一堆 PNG
右键 → Convert to WebP
选择质量,自动生成
2. AAB + Resource Sharding
Android App Bundle(AAB)能让 Google Play 自动按设备分发资源:
android {
bundle {
language {
enableSplit = true // 按语言分包
}
density {
enableSplit = true // 按 dpi 分包
}
abi {
enableSplit = true // 按 ABI 分包
}
}
}
效果:用户只下载匹配自己设备的资源。一个 1080P 设备只下 xxxhdpi 资源,不下载 xxhdpi。
3. 图片压缩工具
Android 资源编译时也会优化,但二次压缩能进一步压。
第四步:Native 库按需打包
1. App Bundle 按 ABI 分包
android {
defaultConfig {
ndk {
abiFilters("arm64-v8a", "armeabi-v7a", "x86_64")
}
}
splits {
abi {
isEnable = true
reset()
include("arm64-v8a", "armeabi-v7a")
isUniversalApk = false
}
}
}
效果:老设备可能只用 armeabi,新设备用 arm64,单 ABI 包大约 5-8MB(基线包)。
2. v7 和 v8 分开打
历史上 armeabi-v7a 设备市场占有率已经 < 10%,可以只保留 arm64:
ndk {
abiFilters("arm64-v8a") // 仅 64 位
}
包体积直接砍掉 30-40%。
3. 移除不必要的 so
很多三方库带了多套 so(每个 ABI 一份),但实际只用了其中一个:
# 看 APK 里都有什么 so
unzip -l app-release.apk | grep '\.so$'
常见操作:
找三方库,看看能不能配置只保留需要的 ABI
用
./gradlew app:bundleRelease让 Play 自动分发国产 ROM 商店可能不支持 ABI split,要测试
第五步:删代码,砍三方库
无用的代码被 R8 自动处理,但实际打包到 dex 的代码不会自动删——R8 是 javacode 级别的优化。要在依赖层面解决:
./gradlew :app:dependencies --configuration releaseRuntimeClasspath
# 看哪些三方库占空间
# 常见结果:event_bus、glide、retrofit、moshi、okhttp...
问自己:
"这个库真的用了 5% 还是 80% 的 API?"
"能不能用现有库替代?"
"能不能 Android SDK 自带的替代?"
我的经验:
第六步:资源按需下载
大体积资源(H5 包、地图离线数据、字体包)不放进 APK,按需下载:
// 把资源放到 OSS,启动时检测版本下载
class ResourceDownloader {
suspend fun downloadResourcesIfNeeded(version: String) {
val localVersion = preferenceStorage.getLocalVersion()
if (version != localVersion) {
val res = apiClient.fetchResourceList(version)
res.forEach { download(it) }
preferenceStorage.setLocalVersion(version)
}
}
}
第七步:动态化
更激进的做法是把某些业务模块做成独立包,按需加载:
Tinker + 自研动态化框架
微信 tinker / 各厂自研
对常规小项目成本太高,对亿级 App ROI 高。
真实案例
一个 30MB 的电商 App 优化过程:
优化误区
1. 过度依赖 R8
R8 强,但不是银弹。它处理的是字节码,不会管你用没用 String、Drawable、ImageView。
2. 删图只删一张
设计师上传的几 MB 大图,三方库的 demo 图,启动引导图……全过一遍。
3. 资源 sharding 不开
AAB + resource sharding 是 Google 推荐姿势,但很多团队没用——理由是"对国内渠道不支持"。
4. 把优化放最后
体积应该在每个 PR 监控,而不是打包发版前突击。
持续监控
把体积加到 CI:
- name: APK Size Check
run: |
SIZE=$(stat -c%s app-release.apk)
PREV=$(cat apk-size.json | jq -r '.size')
if [ $SIZE -gt $((PREV + 500000)) ]; then
echo "APK 增大超过 500KB,请 review!"
exit 1
fi
jq -n --arg size $SIZE --arg ap prev . '{size: $size}' > apk-size.json
小结
APK 瘦身是系统性工程,方法很多:R8、图片格式、ABI 分包、资源 sharding、动态下发。关键是建立监控,每个 PR 都报告 APK 变化。绝大多数 App 都能从 30MB 减到 15MB 左右,不需要架构改动,只需要按步骤执行。
下一篇进入 R8 的更多高级优化,包括混淆、字符串加密、签名校验。