自定义要保留的资源

启用应用优化后,isShrinkResources = true 设置会指示优化器移除未使用的资源,这有助于缩减应用的大小。资源缩减只有在与代码缩减配合使用时才能发挥作用,因此,如果您要优化资源,还应设置 isMinifyEnabled = true,例如:

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

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

  • <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" />

指定要舍弃的资源可能看似没有必要,因为您本可将它们删除,但在使用 build 变体时,舍弃资源可能很有用。

定位特定 build 变体

如需仅移除部分 build 变体中的资源,请将所有资源放入公共项目目录,然后在每个 build 变体的资源目录中为每个 build 变体创建不同的 my.package.build.variant.keep.xml 文件。在 keep 文件中,手动指定要移除的资源,以便在给定资源似乎在代码中使用(因此不会被缩减工具移除)但您知道它实际上不会用于给定 build 变体时,移除该资源。

移除未使用的备用资源

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

在应用的模块 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 对应不同的设备配置,自定义要包含在 APK 中的屏幕密度或 ABI 资源。

避免在合并资源时发生冲突

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

只有在两个或更多个文件具有完全相同的资源名称、类型和限定符时,才会进行资源合并。AGP 会在重复项中选择它认为最合适的文件(根据下述优先顺序),并且只将这一个资源传递给 AAPT,以便在最终 build 工件中分发。

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

  • 与主源代码集关联的主资源,通常位于 src/main/res/
  • 变体叠加,来自 build 类型和 build 变种
  • 库项目依赖项

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

依赖项 → 主资源 → build 变种 → build 类型

例如,如果某个重复资源同时出现在主资源和 build 变种中,Gradle 会选择 build 变种中的资源。

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

排查资源缩减问题

当您缩减资源时,Build 窗口会显示从应用中移除的资源的摘要。(点击窗口左侧的 Toggle view 可显示 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.

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