第3章 资源优化之去除无用资源

你有没有想过,APK 里有多少资源是从来没被用到的?我见过不少项目,drawable 文件夹里躺着几十张图,实际代码中一个都没引用。说白了,这些都是白白占地方的“赘肉”。

这一章我们就专门来清理这些无用资源。别小看这一步,我做过的一个中型应用,光去掉无用的图片和布局文件,包体就小了将近 2MB。你想想看,这几乎没花什么成本。

为什么要清理无用资源?

原因很简单:

  • 减小包体体积 —— 这是最直接的好处
  • 加快编译速度 —— 资源越少,aapt 处理得越快
  • 降低维护负担 —— 团队成员不会对着没人用的资源发懵

我曾在一个老项目里看到一张 800KB 的启动图,检查后发现它早就被新图替换了,但旧图一直留在那里。嗯,这种“资源幽灵”其实很常见。

如何找到无用资源?

手动翻开每个文件夹去排查?不现实。我们通常用两种方法:自动化扫描 + 人工确认。

1. 使用 Android Studio 的 Lint 工具

Lint 是官方内置的静态分析工具。跑一下就能列出所有未被引用的资源。对我来说,这就像给项目做一次“CT 扫描”。

操作步骤:

  1. 在菜单栏选择 Analyze → Inspect Code
  2. 选择整个项目,点击 OK
  3. 在 Inspection 结果框中找到 Android → Lint → Unused resources
  4. 双击每项,跳转到对应文件

你也可以直接在 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 里的名单。不要完全信任自动化,也不要完全信任人工——两者结合才靠谱。

资源优化流程总览

我把整个过程画成了一幅流程图,方便你对照着做:

无用资源优化流程 原始 APK Lint 扫描 审查报告 人工确认 + keep.xml 开启 shrinkResources 构建验证 ✅ 瘦身 APK ⚠️ 构建失败或启动崩溃? 检查 keep.xml 是否遗漏了动态引用资源; 或者在 ProGuard 规则中添加 -keep 类。

我的小技巧: 在第一次开启 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 就能瘦身,实际上它会在混淆之后才执行。切记。

总结

去除无用资源是包体瘦身里性价比最高的操作之一。你只需要:

  1. 跑一遍 Lint 扫描出候选名单
  2. 确认动态引用场景,配置 keep.xml
  3. 开启 shrinkResources,让构建工具自动移除
  4. 构建并测试,确保没有崩溃或页面异常

嗯,这些步骤加起来可能也就半小时,但带来的收益是立竿见影的。别让你的 APK 包养着那些“退休”的资源了。