15、APK瘦身:资源混淆、代码混淆、动态库精简、无用资源删除
APK瘦身这件事,说白了就是跟用户的流量和耐心赛跑。我见过一个项目,APK从30MB一路膨胀到80MB,用户下载转化率直接掉了5个百分点。老板急,我也急。后来我们花了两周时间,把包体砍到了35MB——嗯,今天聊的这几个手段,就是那次瘦身的主力军。
核心思路:APK里无非三样东西——代码、资源、动态库。瘦身就是分别对这三样下手,能删的删,能压缩的压缩,能混淆的混淆。
一、资源混淆:AndResGuard
资源文件(图片、布局、字符串)在打包后,文件名和路径其实是可以被缩短的。AndResGuard做的就是这件事——把 res/drawable-hdpi/ic_launcher.png 变成 r/d/a.png。
我个人习惯在发布Release包时必开这个工具。为什么?因为资源路径的字符串在APK里是明文存储的,缩短后不仅体积变小,还能增加逆向难度。
配置示例(build.gradle)
apply plugin: 'AndResGuard'
andResGuard {
mappingFile = null
use7zip = true
useSign = true
keepRoot = false
// 保留哪些资源不混淆
whiteList = [
"R.string.umeng*",
"R.drawable.umeng*"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.webp"
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.21'
}
}
避坑指南:我曾经遇到过第三方SDK通过资源ID反射获取资源的情况。比如友盟、微信支付,它们内部写死了资源名。这时候一定要加白名单,否则运行时直接崩溃。嗯,别问我怎么知道的。
二、代码混淆:ProGuard / R8
代码混淆不只是为了安全,更是为了瘦身。ProGuard会把类名、方法名、字段名缩短成 a、b、c,同时还会删除无用的代码分支。
R8是Google后来推出的替代品,它比ProGuard更激进,直接在编译期做全量优化。我建议新项目直接用R8,老项目迁移时注意测试。
混淆效果对比
| 项目 | 未混淆 | ProGuard | R8 |
|---|---|---|---|
| DEX体积 | 8.2 MB | 5.1 MB | 4.6 MB |
| 方法数 | 65,432 | 42,108 | 38,976 |
| 类数 | 4,210 | 2,893 | 2,647 |
你看,R8比ProGuard还能再省10%左右。但要注意,R8的优化规则更严格,有些反射调用的代码会被误删。我建议在 proguard-rules.pro 里保留所有通过反射调用的类。
-keep class com.example.reflection.** { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class * extends android.app.Activity
注意:混淆后的堆栈信息是乱码的。一定要保存好 mapping.txt 文件,否则线上崩溃根本看不懂。我习惯每次发版后把这个文件归档到Git仓库的 releases/ 目录下。
三、动态库精简
动态库(.so文件)是APK体积的大头。一个完整的FFmpeg库可能就有20MB。很多开发者图省事,直接把所有架构的so都打进去——arm64-v8a、armeabi-v7a、x86、x86_64,全都要。
你想想看,现在市面上99%的Android设备都是ARM架构,x86基本绝迹了。保留x86的so纯粹是浪费体积。
我的建议
- 只保留 arm64-v8a 和 armeabi-v7a:覆盖99%的设备
- 使用APK拆分:通过
splits配置,为不同架构生成独立APK - 按需加载:有些so库只在特定功能中使用,可以放到服务器上按需下载
android {
splits {
abi {
enable true
reset()
include 'arm64-v8a', 'armeabi-v7a'
universalApk false
}
}
}
这样做之后,每个APK的体积能减少40%-60%。我记得有个项目,原本的so文件夹占了18MB,精简后只剩6MB——效果立竿见影。
四、无用资源删除
项目迭代久了,难免会留下一些废弃的资源。比如某个图片换掉了,但旧图还在drawable里躺着。或者某个布局文件被重构了,但旧的layout文件没人删。
Android Studio自带的 Refactor > Remove Unused Resources 可以扫描出这些垃圾。但我建议不要全选删除——有些资源是通过 getIdentifier() 动态引用的,IDE扫描不出来。
更稳妥的做法
- 先用Lint扫描,生成报告
- 人工确认每个未使用资源是否真的没用
- 使用
shrinkResources true在构建时自动移除
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
小技巧:开启 shrinkResources 后,如果某个资源被误删,可以在 res/raw/keep.xml 中手动保留:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me,@drawable/keep_me"/>
知识体系总览
下面这张图概括了APK瘦身的四个核心方向,以及它们各自的作用范围:
总结一下
APK瘦身不是一次性工作,而是需要持续维护的。我建议每个Release版本都跑一遍这四个步骤:
- 资源混淆:AndResGuard,注意第三方SDK的白名单
- 代码混淆:R8优先,保留反射类和mapping文件
- 动态库精简:只保留ARM架构,按需拆分
- 无用资源删除:Lint扫描 + shrinkResources + 人工确认
嗯,这些手段组合起来,效果远大于单独使用。我记得有一次帮一个合作团队做瘦身,他们之前只做了代码混淆,包体还有45MB。我们把剩下三个手段加上后,直接降到了22MB——对方技术负责人当场就愣住了。
所以别嫌麻烦,该上的工具都上。用户的流量和耐心,都挺贵的。
公众号:蓝海资料掘金营,微信deep3321