在运行时更改应用资源的值

运行时资源叠加层 (RRO) 是一个软件包,可在运行时更改目标软件包的资源值。例如,安装在系统映像上的应用可能会根据资源值更改其行为。安装在不同分区中的 RRO 可能会在运行时更改应用的资源值,而不是在构建时硬编码资源值。

您可以启用或停用 RRO。您可以通过编程方式设置启用/停用状态,以切换 RRO 更改资源值的功能。RRO 默认处于停用状态(但静态 RRO 默认处于启用状态)。

叠加层资源

叠加层的工作原理是将叠加层软件包中定义的资源映射到目标软件包中定义的资源。当应用尝试解析目标软件包中资源的值时,系统转而会返回目标资源映射到的叠加层资源的值。

设置清单

如果某个软件包包含 <overlay> 标记作为 <manifest> 标记的子项,该软件包将被视为 RRO 软件包。

  • 必要 android:targetPackage 属性的值用于指明 RRO 想要叠加的软件包的名称。

  • 可选 android:targetName 属性的值用于指明 RRO 想要叠加的目标软件包的可叠加资源子集的名称。如果目标未定义可叠加资源集,就不应包含此属性。

以下代码展示了一个示例叠加层 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"/>
</manifest>

由于无法叠加代码,因此叠加层无法使用 DEX 文件。此外,必须将清单中 <application> 标记的 android:hasCode 属性设置为 false

定义资源映射

在 Android 11 或更高版本中,用于定义叠加层资源映射的推荐机制是,在叠加层软件包的 res/xml 目录中创建一个文件,枚举应覆盖的目标资源及其替换值,然后将 <overlay> 清单标记的 android:resourcesMap 属性的值设置为对资源映射文件的引用。

以下代码显示了一个示例 res/xml/overlays.xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Overlays string/config1 and string/config2 with the same resource. -->
    <item target="string/config1" value="@string/overlay1" />
    <item target="string/config2" value="@string/overlay1" />

    <!-- Overlays string/config3 with the string "yes". -->
    <item target="string/config3" value="@android:string/yes" />

    <!-- Overlays string/config4 with the string "Hardcoded string". -->
    <item target="string/config4" value="Hardcoded string" />

    <!-- Overlays integer/config5 with the integer "42". -->
    <item target="integer/config5" value="42" />
</overlay>

以下代码显示了一个示例叠加层清单。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

构建软件包

Android 11 或更高版本支持叠加层的 Soong 构建规则,该规则可以防止 Android 资源打包工具 2 (AAPT2) 尝试去除具有相同值的重复资源配置 (--no-resource-deduping) 并移除未采用默认配置的资源 (--no-resource-removal)。以下代码显示了一个示例 Android.bp 文件。

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

解析资源

如果目标资源或叠加层资源为正在查询的资源定义了多个配置,资源运行时将返回与设备配置最匹配的配置值。 如需确定最匹配的配置,请将叠加层资源配置集合并到目标资源配置集中,然后遵循常规资源流程执行操作(如需了解详情,请参阅 Android 如何寻找最匹配的资源)。

例如,如果叠加层为 drawable-en 配置定义了一个值,并且目标为 drawable-en-port 定义了一个值,但 drawable-en-port 的值更匹配,那么在运行时会选择目标配置 drawable-en-port 的值。为了叠加所有 drawable-en 配置,叠加层必须为目标定义的每个 drawable-en 配置定义一个值。

叠加层可以引用自身资源,但对于不同版本的 Android,行为有所不同。

  • 在 Android 11 或更高版本中,每个叠加层都有自己的预留资源 ID 空间,该空间不会与目标资源 ID 空间或其他叠加层资源 ID 空间重叠,因此引用自身资源的叠加层可以按预期运行。

  • 在 Android 10 或更低版本中,叠加层和目标软件包共享同一资源 ID 空间,这可能会导致它们在尝试使用 @type/name 语法引用自身资源时出现冲突和意外行为。

启用/停用叠加层

使用 OverlayManager API 启用和停用可变叠加层(使用 Context#getSystemService(Context.OVERLAY_SERVICE) 检索 API 接口)。叠加层只能由其目标软件包或具有 android.permission.CHANGE_OVERLAY_PACKAGES 权限的软件包启用。在您启用或停用叠加层后,配置更改事件会传播到目标软件包,并且目标 activity 会重新启动。

限制可叠加资源

在 Android 10 或更高版本中,<overlayable> XML 标记公开了一组允许 RRO 叠加的资源。在以下示例 res/values/overlayable.xml 文件中,string/foointeger/bar 是用于为设备的外观设置主题背景的资源;为了叠加这些资源,叠加层必须根据名称明确定位到可叠加资源的集合。

<!-- The collection of resources for theming the appearance of the device -->
<overlayable name="ThemeResources">
       <policy type="public">
               <item type="string" name="foo/" />
               <item type="integer" name="bar/" />
       </policy>
       ...
</overlayable>

一个 APK 可以定义多个 <overlayable> 标记,但每个标记必须在该软件包中具有唯一的名称。例如:

  • 两个不同的软件包可以同时定义 <overlayable name="foo">

  • 一个 APK 不能具有两个 <overlayable name="foo"> 块。

以下代码显示了 AndroidManifest.xml 文件中的一个叠加层示例。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.my.theme.overlay">
       <application android:hasCode="false" />
       <!-- This overlay will override the ThemeResources resources -->
       <overlay android:targetPackage="android" android:targetName="ThemeResources">
</manifest>

当应用定义 <overlayable> 标记时,定位到该应用的叠加层需满足以下条件:

  • 必须指定 targetName

  • 只能叠加 <overlayable> 标记中列出的资源。

  • 只能定位到一个 <overlayable> 名称。

如果某个叠加层定位到的软件包提供可叠加资源,但不使用 android:targetName 定位特定的 <overlayable> 标记,您无法启用该叠加层。

限制政策

使用 <policy> 标记可对可叠加资源施加限制。type 属性指定叠加层必须满足哪些政策的要求才能替换包含的资源。受支持的类型如下。

  • public。任何叠加层均可替换相应资源。
  • system。系统分区上的任何叠加层均可替换相应资源。
  • vendor。vendor 分区上的任何叠加层均可替换相应资源。
  • product。product 分区上的任何叠加层均可替换相应资源。
  • oem。oem 分区上的任何叠加层均可替换相应资源。
  • odm。odm 分区上的任何叠加层均可替换相应资源。
  • signature。使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。
  • actor。使用与 actor APK 相同的签名进行签名的任何叠加层均可替换相应资源。actor 在系统配置中的 named-actor 标记中声明。
  • config_signature。使用与 overlay-config APK 相同的签名进行签名的任何叠加层均可替换相应资源。overlay-config 在系统配置中的 overlay-config-signature 标记中声明。

以下代码显示了 res/values/overlayable.xml 文件中的一个示例 <policy> 标记。

<overlayable name="ThemeResources">
   <policy type="vendor" >
       <item type="string" name="foo" />
   </policy>
   <policy type="product|signature"  >
       <item type="string" name="bar" />
       <item type="string" name="baz" />
   </policy>
</overlayable>

如需指定多个政策,请使用竖线 (|) 作为分隔符。 如果指定了多个政策,叠加层只需满足一个政策的要求即可替换 <policy> 标记中列出的资源。

配置叠加层

Android 支持使用不同的机制以配置叠加层的可变性、默认状态和优先级,具体取决于 Android 发布版本。

  • 搭载 Android 11 或更高版本的设备可以使用 OverlayConfig 文件 (config.xml) 代替清单属性。对于叠加层,推荐使用叠加层文件。

  • 所有设备都可以使用清单属性(android:isStaticandroid:priority)配置静态 RRO。

使用 OverlayConfig

在 Android 11 或更高版本中,您可以使用 OverlayConfig 配置叠加层的可变性、默认状态和优先级。如需配置叠加层,请创建或修改位于 partition/overlay/config/config.xml 的文件,其中 partition 是需配置的叠加层的分区。为了进行配置,叠加层必须位于配置叠加层的分区的 overlay/ 目录中。以下代码显示了一个示例 product/overlay/config/config.xml

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

<overlay> 标记需要 package 属性,该属性指示正在配置哪一个叠加层软件包。可选 enabled 属性控制是否默认启用叠加层(默认为 false)。可选 mutable 属性控制叠加层是否可变,并且在运行时是否可程序化地更改其启用状态(默认为 true)。配置文件中未列出的叠加层是可变的,默认处于停用状态。

叠加层优先级

使用多个叠加层替换同一资源时,必须按适当叠加层顺序进行替换。对叠加层而言,配置越低,优先级越高。叠加层在不同分区的优先级顺序(从最低优先级到最高优先级)如下。

  • system
  • vendor
  • odm
  • oem
  • product
  • system_ext

合并文件

使用 <merge> 标记可将位于指定位置的其他配置文件合并到配置文件中。该标记的 path 属性表示要合并的文件相对于目录(包含叠加层配置文件)的路径。

使用清单属性/静态 RRO

在 Android 10 或更低版本中,使用以下清单属性配置叠加层的不可变性和优先级。

  • android:isStatic。当此布尔值属性的值设置为 true 时,叠加层会默认处于启用状态并且不可变,这会导致叠加层无法停用。

  • android:priority。当多个静态叠加层以相同的资源值为替换目标时,此数字属性的值(仅影响静态叠加层)将配置叠加层的优先级。数值越大表示优先级越高。

以下代码显示了一个示例 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

Android 11 中的变化

在 Android 11 或更高版本中,如果配置文件位于 partition/overlay/config/config.xml 中,叠加层会使用该文件进行配置,并且 android:isStaticandroid:priority 不会影响位于分区中的叠加层。在任何分区中定义叠加层配置文件都会强制执行叠加层分区优先级。

此外,Android 11 或更高版本移除了使用静态叠加层影响软件包安装期间读取的资源值的功能。如需了解使用静态叠加层以更改布尔值(其用于配置组件启用状态)的常见用例,请使用 <component-override> SystemConfig 标记(Android 11 的新变化)。

调试叠加层

如需手动启用、停用和转储叠加层,请使用以下叠加层管理器 shell 命令。

adb shell cmd overlay

OverlayManagerService 使用 idmap2 将目标软件包中的资源 ID 映射到叠加层软件包中的资源 ID。生成的 ID 映射存储在 /data/resource-cache/ 中。如果叠加层无法正常运行,请在 /data/resource-cache/ 中查找叠加层的相应 idmap 文件,然后运行以下命令。

adb shell idmap2 dump --idmap-path [file]

此命令会输出资源的映射,如下所示。

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType