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

运行时资源覆盖 (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>

Overlays 不能覆盖代码,因此它们不能有 DEX 文件。此外,清单中<application > 标记的android:hasCode属性必须设置为false

定义资源图

在Android 11或更高版本中,定义overlay资源映射的推荐机制是在overlay包的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权限的包启用。启用或禁用覆盖时,配置更改事件会传播到目标包并重新启动目标活动。

限制可覆盖资源

在 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 。供应商分区上的任何覆盖都可以覆盖资源。
  • product 。产品分区上的任何覆盖都可以覆盖资源。
  • oem 。 OEM 分区上的任何覆盖都可以覆盖资源。
  • odm . odm 分区上的任何覆盖都可以覆盖资源。
  • signature 。任何与目标 APK 具有相同签名的覆盖都可以覆盖资源。
  • actor 。任何与actor APK 具有相同签名的覆盖都可以覆盖资源。演员在系统配置中的命名演员标签中声明。
  • config_signature 。任何与overlay-config apk具有相同签名的overlay都可以覆盖资源。该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映射到overlay包中的资源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