自定义要保留的资源

启用应用优化后,优化器的默认行为对于不同版本的 R8 是不同的。

  • 借助 AGP 9.3 及更高版本中提供的更新版 DSL,在启用优化时,资源缩减功能默认处于启用状态。请注意,旧版 DSL 仍受支持,但需要明确启用代码和资源优化。
  • 对于 AGP 9.3 之前的版本isShrinkResources = true 设置会指示优化器移除未使用的资源,从而有助于缩减应用的大小。资源缩减只能与代码缩减结合使用,因此如果您要优化资源,还应设置 isMinifyEnabled = true

AGP 9.3 及更高版本 (Kotlin)

buildTypes {
    release {
        optimization {
            enable = true // Enables code and resource optimizations.
        }
    }
}

AGP 9.3 及更高版本 (Groovy)

buildTypes {
    release {
        optimization {
            enable = true // Enables code and resource optimizations.
        }
    }
}

旧版 DSL (Kotlin)

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

旧版 DSL (Groovy)

buildTypes {
    release {
        minifyEnabled = true
        shrinkResources = true
        ...
    }
}

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

  • <resources> 标记 - 包含所有子资源元素和 keep/discard 属性。
  • 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 变体创建一个不同的 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 标记为“可执行到”。在这种情况下,如果您在 build 输出中搜索资源名称,可能会发现如下消息:

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 属性来通知构建系统移除相应资源。