在移动应用开发中,APK 体积直接影响用户下载意愿和留存率。据 Google 统计,APK 每增加 6MB,安装转化率下降 1%。本文将系统讲解 APK 瘦身的完整方案,从资源优化到代码混淆,帮助你将应用体积压缩 50% 以上。
一、APK 体积分析:找到优化切入点
1.1 使用 APK Analyzer 分析构成
Android Studio 内置的 APK Analyzer 是分析工具的首选:
bash
# 通过命令行分析 APK
./gradlew :app:assembleRelease
# 在 Android Studio 中打开 Build > Analyze APK
典型 APK 构成占比:
- resources.arsc(资源索引):10-15%
- classes.dex(代码):20-30%
- res/(图片、布局等):40-50%
- lib/(Native 库):15-25%
1.2 使用脚本快速定位大文件
kotlin
// 自定义 Gradle Task 输出资源占比
tasks.register("analyzeApkSize") {
doLast {
val apkFile = file("$buildDir/outputs/apk/release/app-release.apk")
val entries = mutableMapOf<String, Long>()
java.util.zip.ZipFile(apkFile).use { zip ->
zip.entries().asSequence().forEach { entry ->
val category = when {
entry.name.startsWith("res/drawable") -> "drawable"
entry.name.startsWith("res/layout") -> "layout"
entry.name.startsWith("lib/") -> "native-lib"
entry.name.endsWith(".dex") -> "dex"
else -> "other"
}
entries[category] = entries.getOrDefault(category, 0L) + entry.size
}
}
entries.toList()
.sortedByDescending { it.second }
.forEach { (category, size) ->
println("$category: ${size / 1024 / 1024}MB")
}
}
}
二、资源优化:削减 40% 体积的关键战场
2.1 图片资源压缩与格式选择
WebP 替换 PNG/JPG
WebP 在相同视觉质量下体积减少 25-35%:
groovy
// build.gradle.kts
android {
buildTypes {
release {
// 自动转换 PNG 为 WebP
isCrunchPngs = true
}
}
}
手动转换脚本:
bash
# 批量转换 drawable 目录
find res/drawable* -name "*.png" | while read file; do
cwebp -q 80 "$file" -o "${file%.png}.webp"
rm "$file"
done
矢量图替换位图
对于图标类资源,VectorDrawable 体积仅为 PNG 的 1/10:
xml
<!-- res/drawable/ic_star.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFD700"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>
2.2 资源混淆:减少资源名长度
使用微信开源的 AndResGuard 工具:
groovy
// build.gradle.kts (项目级)
buildscript {
dependencies {
classpath("com.tencent.mm:AndResGuard-gradle-plugin:1.2.21")
}
}
// build.gradle.kts (模块级)
apply(plugin = "AndResGuard")
andResGuard {
mappingFile = file("resguard-mapping.txt")
use7zip = true
useSign = true
keepRoot = false
whiteList = listOf(
"R.drawable.icon", // 保留启动图标
"R.mipmap.*"
)
compressFilePattern = listOf(
"*.png",
"*.jpg",
"*.jpeg",
"*.gif"
)
}
效果对比:
bash
优化前:res/drawable-xhdpi/ic_home_active.png
优化后:res/r/a.png
2.3 多语言与多密度资源裁剪
仅保留必要语言
groovy
android {
defaultConfig {
// 仅保留中文和英文
resourceConfigurations += listOf("zh", "en")
}
}
动态加载高密度资源
仅打包 xxhdpi,其他密度按需下载:
groovy
android {
defaultConfig {
// 仅保留一种密度
resConfigs("xxhdpi")
}
}
三、代码优化:让 DEX 体积减半
3.1 ProGuard/R8 深度混淆
proguard
# proguard-rules.pro
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# 移除无用代码
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
}
# 优化算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
3.2 移除无用依赖
检测未使用的库
bash
./gradlew :app:dependencies > dependencies.txt
# 搜索仅被一个类使用的库,考虑自行实现
替换重型库
| 重型库 | 轻量替代 | 体积减少 |
|---|---|---|
| Glide | Coil | -800KB |
| Gson | Moshi | -500KB |
| OkHttp | HttpURLConnection | -1.2MB |
示例:用 Coil 替换 Glide
kotlin
// 依赖变更
dependencies {
// implementation("com.github.bumptech.glide:glide:4.15.1")
implementation("io.coil-kt:coil:2.4.0") // 减少 800KB
}
// 代码迁移
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
}
3.3 动态特性模块(Dynamic Feature)
将低频功能拆分为按需下载模块:
groovy
// feature-vip/build.gradle.kts
plugins {
id("com.android.dynamic-feature")
}
android {
namespace = "com.example.feature.vip"
}
dependencies {
implementation(project(":app"))
}
按需加载逻辑
kotlin
class FeatureInstaller(private val context: Context) {
private val splitInstallManager = SplitInstallManagerFactory.create(context)
fun installVipFeature(onSuccess: () -> Unit) {
val request = SplitInstallRequest.newBuilder()
.addModule("feature_vip")
.build()
splitInstallManager.startInstall(request)
.addOnSuccessListener { sessionId ->
monitorInstall(sessionId, onSuccess)
}
.addOnFailureListener { e ->
Log.e("Installer", "安装失败", e)
}
}
private fun monitorInstall(sessionId: Int, onSuccess: () -> Unit) {
splitInstallManager.registerListener { state ->
if (state.sessionId() == sessionId) {
when (state.status()) {
SplitInstallSessionStatus.INSTALLED -> {
onSuccess()
splitInstallManager.unregisterListener(this)
}
}
}
}
}
}
四、Native 库优化:精准瘦身
4.1 ABI 过滤
现代设备 95% 支持 arm64-v8a:
groovy
android {
defaultConfig {
ndk {
// 仅保留主流架构
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
}
4.2 提取公共 SO 库
当多个架构的 SO 文件相似时,可提取公共部分:
kotlin
// 自定义 Task 压缩 SO
tasks.register("compressNativeLibs") {
doLast {
val libDir = file("$buildDir/intermediates/merged_native_libs/release/out/lib")
libDir.listFiles()?.forEach { abiDir ->
exec {
commandLine("upx", "--best", "--lzma", abiDir.absolutePath + "/*.so")
}
}
}
}
五、实战案例:电商 App 从 50MB 到 15MB
优化前后对比
| 优化项 | 优化前 | 优化后 | 减少 |
|---|---|---|---|
| 图片资源 | 25MB | 8MB | -17MB |
| DEX 代码 | 12MB | 4MB | -8MB |
| Native 库 | 10MB | 2MB | -8MB |
| 其他资源 | 3MB | 1MB | -2MB |
| 总计 | 50MB | 15MB | -35MB |
关键配置清单
groovy
// build.gradle.kts
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
defaultConfig {
resourceConfigurations += listOf("zh", "xxhdpi")
ndk.abiFilters += listOf("arm64-v8a")
}
packagingOptions {
// 移除重复文件
resources.excludes += listOf(
"META-INF/*.kotlin_module",
"META-INF/*.version"
)
}
}
六、持续监控:防止体积反弹
6.1 CI/CD 集成体积检查
yaml
# .github/workflows/apk-size-check.yml
name: APK Size Check
on: [pull_request]
jobs:
check-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build APK
run: ./gradlew assembleRelease
- name: Check APK Size
run: |
SIZE=$(stat -c%s app/build/outputs/apk/release/app-release.apk)
MAX_SIZE=$((20 * 1024 * 1024)) # 20MB 上限
if [ $SIZE -gt $MAX_SIZE ]; then
echo "APK 超过 20MB 限制:${SIZE}字节"
exit 1
fi
6.2 Gradle 插件自动告警
kotlin
// buildSrc/src/main/kotlin/ApkSizePlugin.kt
class ApkSizePlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("checkApkSize") {
doLast {
val apkFile = project.file("${project.buildDir}/outputs/apk/release/app-release.apk")
val sizeMB = apkFile.length() / 1024 / 1024
if (sizeMB > 20) {
throw GradleException("APK 体积 ${sizeMB}MB 超过 20MB 阈值!")
}
println("✅ APK 体积检查通过:${sizeMB}MB")
}
}
}
}
七、总结
APK 瘦身是一项系统工程,核心策略包括:
- 资源优化:WebP 替换、矢量图、资源混淆、密度裁剪
- 代码优化:R8 混淆、移除无用依赖、动态特性模块
- Native 优化:ABI 过滤、SO 压缩
- 持续监控:CI/CD 集成、自动化告警
通过本文方案,可将 APK 体积压缩 50% 以上。建议从资源优化入手(见效最快),再逐步深入代码和 Native 层。记住,每减少 6MB,安装转化率就能提升 1%。