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

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