在運行時更改應用資源的值

運行時資源覆蓋 (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