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

运行时资源覆盖 (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或更高版本中,定义覆盖资源映射的推荐机制是在覆盖包的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配置定义一个值。

Overlays 可以引用自己的资源,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 。任何使用与参与者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 )。未在配置文件中列出的叠加层是可变的,默认情况下是禁用的。

覆盖优先级

当多个覆盖层覆盖相同的资源时,覆盖层的顺序很重要。覆盖比其配置先于自身配置的覆盖具有更高的优先级。不同分区中overlay的优先顺序(从低到高)如下。

  • 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