自定义要保留的资源

当您启用应用优化时,isShrinkResources = true 设置会指示优化器移除未使用的资源,这有助于减小应用的大小。资源收缩仅与代码收缩结合使用时才有效,因此如果您要优化资源,也要设置 isMinifyEnabled = true,例如:

buildTypes {
    release {
        isMinifyEnabled = true
        isShrinkResources = true
        ...
    }
}

如果您想保留或丢弃特定资源,请在您的项目资源中创建一个 XML“保留”文件,例如 res/raw/my.package.keep.xml。保留文件包含以下组件:

  • <resources> 标记 — 包含所有子资源元素和保留/丢弃属性。
  • tools:keep 属性 — 接受以逗号分隔的资源名称列表,用于标识要保留的资源。
  • tools:discard 属性 — 接受以逗号分隔的资源名称列表,用于标识要丢弃的资源。

使用星号字符作为通配符来引用同一文件夹中的多个资源,例如:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

指定要丢弃的资源可能看起来多余,因为您可以直接删除它们,但丢弃资源在使用构建变体时可能很有用。

定位特定构建变体

要仅在某些构建变体中移除资源,请将所有资源放入通用项目目录,然后为每个构建变体在其资源目录中创建一个不同的 my.package.build.variant.keep.xml 文件。在保留文件中,手动指定当某个资源在代码中看似被使用(因此不会被收缩器移除),但您知道它实际上不会用于给定构建变体时,要移除的资源。

移除未使用的替代资源

优化器只会移除未被应用代码引用的资源,这意味着优化器不会移除针对不同设备配置的替代资源

在您应用的模块 build.gradle 文件中使用 Android Gradle resConfigs 属性来移除您的应用不需要的替代资源文件。

例如,如果您正在使用包含语言资源(如 Google Play 服务)的库,那么您的应用将包含这些库中所有已翻译的消息语言字符串,无论您的应用其余部分是否已翻译成相同的语言。要仅保留您的应用正式支持的语言,请使用 resConfigs 属性指定这些语言。未指定语言的所有资源都将被移除。

以下代码段展示了如何将您的语言资源限制为仅英语和法语:

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

当您使用 Android App Bundle (AAB) 格式发布应用时,默认情况下,当用户安装应用时,只会下载用户设备上配置的语言。同样,下载中也只会包含与设备屏幕密度匹配的资源和与设备 ABI 匹配的原生库。如需了解更多信息,请参阅重新启用或停用配置 APK 类型

对于使用 APK(在 2021 年 8 月之前创建)发布的旧版应用,您可以通过构建多个 APK 来针对不同的设备配置,从而自定义要包含在 APK 中的屏幕密度或 ABI 资源。

合并资源时避免冲突

默认情况下,Android Gradle 插件 (AGP) 会合并同名资源,例如在不同资源文件夹中具有相同名称的 drawable。此行为不受 shrinkResources 属性控制,也无法禁用,因为在多个资源具有代码引用的名称时,此行为对于避免错误是必要的。

资源合并仅在两个或更多文件共享相同的资源名称、类型和限定符时发生。AGP 会从重复项中选择其认为的最佳选项(基于下述优先级顺序),并仅将该资源传递给 AAPT,以便在最终构建工件中分发。

AGP 会在以下位置查找重复资源:

  • 主资源,与主源集关联,通常位于 src/main/res/
  • 变体叠加层,来自构建类型和构建风格
  • 库项目依赖项

AGP 按照以下级联优先级顺序合并重复资源:

依赖项 → 主 → 构建风格 → 构建类型

例如,如果主资源和构建风格中都出现重复资源,Gradle 会选择构建风格中的资源。

如果相同资源出现在同一个源集中,Gradle 无法合并它们并会发出资源合并错误。当您在模块 build.gradle 文件的 sourceSet 属性中定义多个源集时,可能会发生这种情况,例如,如果 src/main/res/src/main/res2/ 都包含相同的资源。

解决资源收缩问题

当您收缩资源时,“构建”窗口会显示从应用中移除的资源的摘要。(点击窗口左侧的“切换视图”可显示 Gradle 的详细文本输出。)例如:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle 还在 <module-name>/build/outputs/mapping/release/(与 ProGuard 的输出文件相同的文件夹)中创建一个名为 resources.txt 的诊断文件。该文件包含详细信息,例如哪些资源引用了其他资源,以及哪些资源被使用或移除。

例如,要找出 @drawable/ic_plus_anim_016 为何仍在您的应用中,请打开 resources.txt 文件并搜索该文件名。您可能会发现它被另一个资源引用:

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016

您现在需要知道为什么 @drawable/add_schedule_fab_icon_anim 可以访问;如果您向上搜索,您会发现该资源列在 resources.txt 的“可访问的根资源是:”标题下。

这意味着代码中引用了 add_schedule_fab_icon_anim,也就是说,它的 R.drawable ID 在可访问的代码中被找到。

除非您使用严格检查,否则如果字符串常量看起来可能用于构造动态加载资源的资源名称,资源 ID 可能会被标记为可访问。在这种情况下,如果您在构建输出中搜索资源名称,您可能会找到如下消息:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because its format-string matches string pool constant ic_plus_anim_%1$d.

如果您看到其中一个字符串,并且您确定该字符串未用于动态加载给定资源,请在您的保留文件中使用 tools:discard 属性来通知构建系统移除该资源。