第3章 资源优化之去除无用资源
你有没有想过,APK 里有多少资源是从来没被用到的?我见过不少项目,drawable 文件夹里躺着几十张图,实际代码中一个都没引用。说白了,这些都是白白占地方的“赘肉”。
这一章我们就专门来清理这些无用资源。别小看这一步,我做过的一个中型应用,光去掉无用的图片和布局文件,包体就小了将近 2MB。你想想看,这几乎没花什么成本。
为什么要清理无用资源?
原因很简单:
- 减小包体体积 —— 这是最直接的好处
- 加快编译速度 —— 资源越少,aapt 处理得越快
- 降低维护负担 —— 团队成员不会对着没人用的资源发懵
我曾在一个老项目里看到一张 800KB 的启动图,检查后发现它早就被新图替换了,但旧图一直留在那里。嗯,这种“资源幽灵”其实很常见。
如何找到无用资源?
手动翻开每个文件夹去排查?不现实。我们通常用两种方法:自动化扫描 + 人工确认。
1. 使用 Android Studio 的 Lint 工具
Lint 是官方内置的静态分析工具。跑一下就能列出所有未被引用的资源。对我来说,这就像给项目做一次“CT 扫描”。
操作步骤:
- 在菜单栏选择
Analyze → Inspect Code - 选择整个项目,点击 OK
- 在 Inspection 结果框中找到
Android → Lint → Unused resources - 双击每项,跳转到对应文件
你也可以直接在 build.gradle 里配置 Lint 任务,在 CI 构建时自动检查:
android {
lintOptions {
checkReleaseBuilds true
abortOnError false
// 启用无用资源检查
check 'UnusedResources'
}
}
我个人习惯在 release 构建前跑一遍 Lint,把警告打成报告给团队审查。
2. 使用资源缩减(Resource Shrinking)
Android Gradle 插件从 2.0 开始就支持自动移除无用资源。你只需要在 build.gradle 里开启:
android {
buildTypes {
release {
minifyEnabled true // 代码混淆
shrinkResources true // 资源缩减
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
开启 shrinkResources 后,构建时会自动删除未被代码引用的资源。但要注意:它不会删除通过反射或动态 ID 引用的资源。这就像扫地机器人扫不到墙角——你得自己处理。
我曾经踩过的坑: 有次开启了 shrinkResources,结果 App 启动就崩溃。原因是某个第三方库通过字符串拼接获取资源 ID,Lint 以为这些资源是多余的,就给删了。从那以后我学乖了——务必在 ProGuard 规则里保留那些动态引用的资源。
3. 手动审查“可能被误删”的资源
哪些情况会让自动扫描漏掉或错判?
- 反射调用:
Resources.getIdentifier()动态获取的资源 - WebView 中使用的本地 HTML/图片:HTML 里的资源引用 Lint 可能检测不到
- 库模块里的资源:如果主工程不直接引用某个库里的资源,但库本身需要它,也会被误删
- keep.xml 文件:用于显式告知哪些资源必须保留
针对这些情况,你可以在 res/raw/ 下创建一个 keep.xml 文件(没有就新建):
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/splash_bg,@layout/webview_activity,@mipmap/icon_v2" />
记住一个小原则: 先让工具扫描一遍,然后人工过一遍 keep.xml 里的名单。不要完全信任自动化,也不要完全信任人工——两者结合才靠谱。
资源优化流程总览
我把整个过程画成了一幅流程图,方便你对照着做:
我的小技巧: 在第一次开启 shrinkResources 时,先不要删任何东西。对比一下构建后的 APK 大小和 Lint 报告,看看哪些资源被移除了。然后打开生成的 build/outputs/mapping/release/resources.txt 文件,里面详细记录了每个资源是被移除还是被保留。这会让你对整个机制更有把握。
不同资源类型的优化效果
一张表告诉你各种资源对包体的“贡献”有多大:
| 资源类型 | 常见膨胀源 | 瘦身后可节省 | 手动检查必要性 |
|---|---|---|---|
| 图片 (drawable/mipmap) | 未压缩的 PNG、多套分辨率冗余 | 30%~70% | 高 |
| 布局文件 (layout) | 已弃用的 Activity/Fragment 的布局 | 几 KB 到几百 KB | 中 |
| 字符串 (values/strings.xml) | 旧文案、测试用字符串 | 忽略不计,但影响 APK 大小 | 低 |
| 动画 (anim/animator) | 废弃的转场动画 | 几 KB | 低 |
| 原始文件 (raw) | 内置的旧版 HTML/JSON/字体 | 视情况,可能几百 KB | 高 |
图片是无用资源里的“大头”。我记得有个项目,设计师换了一整套图标,但旧图标文件还按着原来的命名保留在 mipmap 里。Lint 跑出来足足 1.2MB 的冗余。删掉后,包体直接瘦了一大圈。
避坑指南
我曾经遇到过最头疼的问题: 某个资源在代码中没有直接引用,但被 WebView 通过 file:///android_res/ 协议加载。Lint 认为它是无用的,就给清理掉了。结果上线后用户反馈某个页面图片显示不出来。所以,如果项目里有 WebView 加载本地资源的情况,一定要手动在 keep.xml 里声明。
另外,shrinkResources 与 minifyEnabled 是绑定关系。不开启混淆,资源缩减也不会生效。很多人以为只开 shrinkResources true 就能瘦身,实际上它会在混淆之后才执行。切记。
总结
去除无用资源是包体瘦身里性价比最高的操作之一。你只需要:
- 跑一遍 Lint 扫描出候选名单
- 确认动态引用场景,配置 keep.xml
- 开启 shrinkResources,让构建工具自动移除
- 构建并测试,确保没有崩溃或页面异常
嗯,这些步骤加起来可能也就半小时,但带来的收益是立竿见影的。别让你的 APK 包养着那些“退休”的资源了。