運行時資源覆蓋 (RRO)

運行時資源覆蓋 (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> manifest 標籤的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 。產品分區上的任何覆蓋都可以覆蓋資源。
  • signature 。使用與目標 APK 相同的簽名簽名的任何覆蓋都可以覆蓋資源。

以下代碼顯示了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
  • oem
  • odm
  • 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