[Android 从零到一] APK 体积优化实战:从 50MB 到 15MB 的瘦身之旅

在移动应用开发中,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 瘦身是一项系统工程,核心策略包括:

  1. 资源优化:WebP 替换、矢量图、资源混淆、密度裁剪
  2. 代码优化:R8 混淆、移除无用依赖、动态特性模块
  3. Native 优化:ABI 过滤、SO 压缩
  4. 持续监控:CI/CD 集成、自动化告警

通过本文方案,可将 APK 体积压缩 50% 以上。建议从资源优化入手(见效最快),再逐步深入代码和 Native 层。记住,每减少 6MB,安装转化率就能提升 1%。

相关推荐
迪飞特科技19 分钟前
《MyBatis 批量插入性能优化的 5 个关键参数配置》
android·性能优化·mybatis
Coodor23 分钟前
在前端如何转换IC卡卡号格式
前端·javascript·nfc·卡号格式
invicinble30 分钟前
数字系统--c端数字环境入口和操作系统
前端
hunterandroid33 分钟前
[鸿蒙从零到一] relationalStore 高级实战:事务、版本迁移与数据库加密
前端
百慕大三角37 分钟前
给 SPA 加页面切换动画:View Transitions API 从入门到实战
前端·javascript
不可能片场38 分钟前
自动化也会丢:一次 WorkBuddy 自动化清空的复盘
前端·electron
求道於盲42 分钟前
异步编程中的 Future
前端
mmsx43 分钟前
osmdroid 手势交互与罗盘方向:缩放联动+传感器融合+定位跟随
android·源码·地图·osmdroid
Z兽兽1 小时前
HBuilder打包web地址的apk,更新dist后如何自动清缓存
前端·android应用打包