在移动应用开发中,APK 体积直接影响用户下载意愿和留存率。据 Google 统计,APK 每增加 6MB,安装转化率下降 1%。本文将系统讲解 APK 瘦身的完整方案,从资源优化到代码混淆,帮助你将应用体积压缩 50% 以上。
Android Studio 内置的 APK Analyzer 是分析工具的首选:
# 通过命令行分析 APK
./gradlew :app:assembleRelease
# 在 Android Studio 中打开 Build > Analyze APK典型 APK 构成占比:
// 自定义 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")
}
}
}WebP 替换 PNG/JPG
WebP 在相同视觉质量下体积减少 25-35%:
// build.gradle.kts
android {
buildTypes {
release {
// 自动转换 PNG 为 WebP
isCrunchPngs = true
}
}
}手动转换脚本:
# 批量转换 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:
<!-- 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>使用微信开源的 AndResGuard 工具:
// 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"
)
}效果对比:
优化前:res/drawable-xhdpi/ic_home_active.png
优化后:res/r/a.png仅保留必要语言
android {
defaultConfig {
// 仅保留中文和英文
resourceConfigurations += listOf("zh", "en")
}
}动态加载高密度资源
仅打包 xxhdpi,其他密度按需下载:
android {
defaultConfig {
// 仅保留一种密度
resConfigs("xxhdpi")
}
}# 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/*检测未使用的库
./gradlew :app:dependencies > dependencies.txt
# 搜索仅被一个类使用的库,考虑自行实现替换重型库
重型库 | 轻量替代 | 体积减少 |
|---|---|---|
Glide | Coil | -800KB |
Gson | Moshi | -500KB |
OkHttp | HttpURLConnection | -1.2MB |
示例:用 Coil 替换 Glide
// 依赖变更
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)
}将低频功能拆分为按需下载模块:
// feature-vip/build.gradle.kts
plugins {
id("com.android.dynamic-feature")
}
android {
namespace = "com.example.feature.vip"
}
dependencies {
implementation(project(":app"))
}按需加载逻辑
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)
}
}
}
}
}
}现代设备 95% 支持 arm64-v8a:
android {
defaultConfig {
ndk {
// 仅保留主流架构
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
}当多个架构的 SO 文件相似时,可提取公共部分:
// 自定义 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")
}
}
}
}优化项 | 优化前 | 优化后 | 减少 |
|---|---|---|---|
图片资源 | 25MB | 8MB | -17MB |
DEX 代码 | 12MB | 4MB | -8MB |
Native 库 | 10MB | 2MB | -8MB |
其他资源 | 3MB | 1MB | -2MB |
总计 | 50MB | 15MB | -35MB |
// 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"
)
}
}# .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// 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 瘦身是一项系统工程,核心策略包括:
通过本文方案,可将 APK 体积压缩 50% 以上。建议从资源优化入手(见效最快),再逐步深入代码和 Native 层。记住,每减少 6MB,安装转化率就能提升 1%。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。