如何排查rro资源overlay的问题?

一、什么是RRO?

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

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

1.1 叠加资源

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

1.1.1 设置清单

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

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

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

1
2
3
4
5
6
<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

1.1.2 定义资源映射

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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>

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

1
2
3
4
5
6
7
<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 10 或更低版本中,系统根据资源的名称叠加资源。为了在目标软件包中叠加资源 string/foo,叠加层还必须定义资源 string/foo。

1.1.3 构建软件包

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

1
2
3
4
runtime_resource_overlay {
name: "ExampleOverlay",
sdk_version: "current",
}

1.1.4 解析资源

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

例如,如果叠加层为 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 语法引用自身资源时出现冲突和意外行为。

1.1.5 启用/停用叠加层

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

1.2 限制可叠加资源

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

1
2
3
4
5
6
7
8
<!-- 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 文件中的一个叠加层示例。

1
2
3
4
5
6
<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> 标记,您无法启用该叠加层。

注意:如果目标软件包没有定义的可叠加资源,必须将该叠加层预安装在系统映像上,或使用与目标软件包相同的签名对该叠加层进行签名,以便叠加目标软件包的资源。

1.2.1 限制政策

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

  • public : 任何叠加层均可替换相应资源。
  • system : 系统分区上的任何叠加层均可替换相应资源。
  • vendor : vendor 分区上的任何叠加层均可替换相应资源。
  • product : product 分区上的任何叠加层均可替换相应资源。
  • signature : 使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。

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

1
2
3
4
5
6
7
8
9
<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> 标记中列出的资源。

1.3 配置叠加层

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

  • 搭载 Android 11 或更高版本的设备可以使用 OverlayConfig 文件 (config.xml) 代替清单属性。对于叠加层,推荐使用叠加层文件。
  • 所有设备都可以使用清单属性(android:isStatic 和 android:priority)配置静态 RRO。

注意:只有定位到 android 软件包的不可变叠加层才会影响通过 Resources.getSystem() 检索的资源值。

1.3.1 使用 OverlayConfig

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

1
2
3
4
5
<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)。配置文件中未列出的叠加层是可变的,默认处于停用状态。

1.3.1.1 叠加层优先级

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

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

    注意:当 OverlayManagerService 启用未配置的叠加层时,系统不会定义叠加层的顺序。

1.3.1.2 合并文件

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

1.3.2 使用清单属性(静态 RRO)

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

  • android:isStatic。当此布尔值属性的值设置为 true 时,叠加层会默认处于启用状态并且不可变,这会导致叠加层无法停用。
  • android:priority。当多个静态叠加层以相同的资源值为替换目标时,此数字属性的值(仅影响静态叠加层)将配置叠加层的优先级。数值越大表示优先级越高。

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

1
2
3
4
5
6
7
<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 的新变化)。

1.4 调试叠加层

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

1
adb shell cmd overlay

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

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

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

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

二、排查RRO问题

2.1 列出所有的RRO

2.1.1 cmd overlay list指令

1
adb shell cmd overlay list --user current

系统会显示类似如下文本的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
spring:/ # cmd overlay list --user current
com.android.uwb.resources
[x] com.android.uwb.resources.overlay.common

com.android.providers.telephony
--- com.android.providers.telephony.auto_generated_characteristics_rro

com.android.companiondevicemanager
--- com.android.companiondevicemanager.auto_generated_characteristics_rro

com.android.carrierconfig
[x] com.android.carrierconfig.overlay.common

android
[x] android.overlay.target
--- android.auto_generated_characteristics_rro
[x] android.overlay.common
--- android.qvaoverlay.common
[ ] com.android.internal.display.cutout.emulation.corner
[ ] com.android.internal.display.cutout.emulation.double
[ ] com.android.internal.display.cutout.emulation.hole
[ ] com.android.internal.display.cutout.emulation.tall
[ ] com.android.internal.systemui.navbar.threebutton
[ ] com.android.theme.font.notoserifsource
[ ] com.android.internal.display.cutout.emulation.waterfall
[ ] com.android.internal.systemui.navbar.transparent
[ ] com.android.role.notes.enabled
[ ] com.android.internal.systemui.navbar.gestural
[x] com.android.systemui:neutral
[x] com.android.systemui:accent
[x] com.android.systemui:dynamic

com.android.se
[x] com.android.se.overlay.target

com.android.cellbroadcastreceiver
--- com.android.cellbroadcastreceiver.overlay.common

com.android.server.telecom
[x] com.android.server.telecom.overlay.common

com.android.wifi.resources
[x] com.android.wifi.resources.overlay.target
[x] com.android.wifi.resources.overlay.common

com.android.networkstack.tethering
[x] com.qualcomm.qti.server.wigig.tethering.rro

com.android.settings
[x] com.android.settings.overlay.common

com.google.android.wifi.resources
--- com.google.android.wifi.resources.overlay.target
--- com.google.android.wifi.resources.overlay.common

com.qualcomm.location.XT
--- com.qualcomm.qti.optinoverlay

com.android.phone
--- com.android.phone.auto_generated_characteristics_rro
[x] com.android.phone.overlay.common

com.android.systemui
[x] com.android.systemui.overlay.common

2.1.2 cmd overlay list输出结果解释

指标 RRO 状态
[ ] 已安装,待激活。
[X] 已安装,已激活。
已安装,但包含错误。

如果相应 RRO 未在叠加目标的软件包名称下方列出,则表明该 RRO 尚未安装

2.2 启用和停用RRO

使用以下命令启用(或停用)RRO:

1
adb shell cmd overlay [enable/disable] --user current [your RRO package name]

注意:您可以将 current 替换为您的用户。例如,您可以将其替换为 0 或 10,也可以保持原值不变。
注意:静态 RRO 在启动时默认处于启用状态。默认情况下,系统在启动时不会启用动态 RRO。这些默认行为也有一些例外情况。如需了解详情,请参阅运行时资源叠加层 (RRO)。

2.3 确认是否已安装 RRO

  1. 如需确认设备上是否已安装 RRO,或要排查 RRO 未启用的原因,请执行以下操作
1
adb shell cmd overlay dump [your RRO package name]

会显示类似如下文本的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
spring:/ # cmd overlay dump android.overlay.target
android.overlay.target:0 {
mPackageName...........: android.overlay.target
mOverlayName...........: null
mUserId................: 0
mTargetPackageName.....: android
mTargetOverlayableName.: null
mBaseCodePath..........: /vendor/overlay/FrameworksResTarget_Vendor.apk
mState.................: STATE_ENABLED
mIsEnabled.............: true
mIsMutable.............: false
mPriority..............: 1
mCategory..............: null
mIsFabricated..........: false
}
IDMAP OF android.overlay.target
Paths:
target path : /system/framework/framework-res.apk
overlay path : /vendor/overlay/FrameworksResTarget_Vendor.apk
Mapping:
0x0104027b -> 0x7f040000 (string/config_mainDisplayShape -> string/config_mainDisplayShape)
0x010502dd -> 0x7f030000 (dimen/rounded_corner_content_padding -> dimen/rounded_corner_content_padding)
0x010502de -> 0x7f030001 (dimen/rounded_corner_radius -> dimen/rounded_corner_radius)
0x0107003f -> 0x7f010000 (array/config_defaultPinnerServiceFiles -> array/config_defaultPinnerServiceFiles)
0x01110266 -> 0x7f020000 (bool/config_useDevInputEventForAudioJack -> bool/config_useDevInputEventForAudioJack)
0x0117000a -> 0x7f050000 (xml/irq_device_map -> xml/irq_device_map)
  1. 确定安装这个 RRO 的用户。在上面的示例中,RRO 适用于用户 0 和用户 10(参见顶部代码块中 mUserId 的值)。

  2. 如需为预期用户启用(或停用)RRO,请转到第 2 步。

  3. 如需查看 mState 的值,请执行以下操作:

    • STATE_ENABLED 和 STATE_ENABLED_IMMUTABLE。RRO 已启用,并已应用于您的目标。
    • STATE_MISSING_TARGET。您的目标尚未安装。
    • STATE_NO_IDMAP。AndroidManifest.xml、overlays.xml 或 overlayable.xml 文件的设置方式存在问题。您可以使用 adb logcat 运行日志,并搜索关键字“idmap”,找出错误所在。请参阅第 4 步和第 5 步
    • STATE_UNKNOWN。OverlayManagerService 出现了问题。

2.4 检查 AndroidManifest.xml

为验证 AndroidManifest.xml,请执行以下操作:

  1. 检查 targetNametargetPackage
    android:targetName 的值应与目标应用中定义的可叠加组相同。只有在定位叠加层的情况下才需满足此要求。
    android:targetPackage 始终是必需项,并且应包含目标应用的软件包名称。
  2. 检查您的 RRO 是否处于静态。默认情况下,系统在启动时会启用静态 RRO。默认情况下,系统在启动时不会启用动态 RRO。运行时资源叠加层 (RRO) 中提供了启用动态 RRO 的其他方法。
  3. 检查静态 RRO 的优先级(动态 RRO 优先级始终设置为 Integer.MAX_VALUE,并且它们的应用顺序取决于其启用时间)。
    对于同一目标,可以应用多个 RRO。RRO 的优先级越高,应用顺序越靠后。优先级范围为 0 到 10,其中 10 代表最高,0 代表最低。

2.5 检查overlays.xml

这项检查仅适用于 Android 11(及更高版本)。

  1. 请检查 overlays.xml,确认该文件中是否已定义您要叠加的所有资源。例如,请考虑以下 overlays.xml
1
2
3
<overlay>
<item target="string/app_name" value="@string/overlaid_app_name" />
</overlay>
  1. 您必须确保:
    • 目标应用中存在名为 app_namestring 资源。
    • 您的 RRO 中存在名为 overlaid_app_namestring 资源。
  2. 如果您的目标包含 overlayable.xml 文件,请确保该文件中包含 app_name。请务必在 AndroidManifest.xml 文件中使用正确的 targetName(第 4 步)。

如果您叠加布局文件,请确保 overlays.xml 和 overlayable.xml 中均包含所有 ID 和应用命名空间属性。

2.6 转储idmap

在此阶段,RRO 的所有问题都应已解决。接下来,请转储 RRO 的 idmap,以了解资源的解析情况;如果资源解析值与您的预期不同,您还可以从中了解原因。

  1. 如需在设备上查找 idmap 所在的路径,请执行以下操作:

    1
    2
    3
    adb root
    adb shell
    ls data/resource-cache
  2. 如需转储该文件的内容,请执行以下操作

    1
    idmap2 dump --idmap-path [path to your RRO idmap file]

    输出的内容应类似于如下文本。输出会显示 RRO 中的各个 ID 与目标中的具体 ID 之间的映射对应关系,以及叠加资源的名称。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:/data/resource-cache # idmap2 dump --idmap-path vendor@overlay@FrameworksResTarget_Vendor.apk@idmap
    Paths:
    target path : /system/framework/framework-res.apk
    overlay path : /vendor/overlay/FrameworksResTarget_Vendor.apk
    Mapping:
    0x0104027b -> 0x7f040000 (string/config_mainDisplayShape -> string/config_mainDisplayShape)
    0x010502dd -> 0x7f030000 (dimen/rounded_corner_content_padding -> dimen/rounded_corner_content_padding)
    0x010502de -> 0x7f030001 (dimen/rounded_corner_radius -> dimen/rounded_corner_radius)
    0x0107003f -> 0x7f010000 (array/config_defaultPinnerServiceFiles -> array/config_defaultPinnerServiceFiles)
    0x01110266 -> 0x7f020000 (bool/config_useDevInputEventForAudioJack -> bool/config_useDevInputEventForAudioJack)
    0x0117000a -> 0x7f050000 (xml/irq_device_map -> xml/irq_device_map)

三、实例

在vendor侧集成power_profile.xml的overlay。
AndroidManifst.xml如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright (c) 2019-2021, Qualcomm Technologies, Inc.
All Rights Reserved.
Confidential and Proprietary - Qualcomm Technologies, Inc.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.overlay.target">
<overlay android:targetPackage="android"
android:isStatic="true"
android:priority="1500"
android:requiredSystemPropertyName="ro.power.profile"
android:requiredSystemPropertyValue="spring_1"/>
</manifest>

3.1 cmd overlay check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
spring:/ # cmd overlay dump android.overlay.target
android.overlay.target:0 {
mPackageName...........: android.overlay.target
mOverlayName...........: null
mUserId................: 0
mTargetPackageName.....: android
mTargetOverlayableName.: null
mBaseCodePath..........: /vendor/overlay/FrameworksResTarget_Vendor.apk
mState.................: STATE_ENABLED
mIsEnabled.............: true
mIsMutable.............: false
mPriority..............: 1
mCategory..............: null
mIsFabricated..........: false
}
IDMAP OF android.overlay.target
Paths:
target path : /system/framework/framework-res.apk
overlay path : /vendor/overlay/FrameworksResTarget_Vendor.apk
Mapping:
0x0104027b -> 0x7f040000 (string/config_mainDisplayShape -> string/config_mainDisplayShape)
0x010502dd -> 0x7f030000 (dimen/rounded_corner_content_padding -> dimen/rounded_corner_content_padding)
0x010502de -> 0x7f030001 (dimen/rounded_corner_radius -> dimen/rounded_corner_radius)
0x0107003f -> 0x7f010000 (array/config_defaultPinnerServiceFiles -> array/config_defaultPinnerServiceFiles)
0x01110266 -> 0x7f020000 (bool/config_useDevInputEventForAudioJack -> bool/config_useDevInputEventForAudioJack)
0x0117000a -> 0x7f050000 (xml/irq_device_map -> xml/irq_device_map)
0x01170013 -> 0x7f050001 (xml/power_profile.xml -> xml/power_profile.xml) //pass

3.2 dumpsys batterystats

1
dumpsys batterystats --power-profile