在執行階段變更應用程式的值('s)

執行階段資源覆蓋 (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> 資訊清單標記的 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 語法參照自身資源時發生非預期的行為。

啟用/停用重疊顯示

重疊層可透過手動和程式設計方式啟用/停用。

手動停用或啟用覆蓋層

如要手動啟用及驗證 RRO,請執行:

adb shell cmd overlay enable --user current com.example.carrro
adb shell cmd overlay list --user current | grep -i com.example com.example.carrro

這會為擁有 SystemUI 的系統使用者 (userId = 0) 啟用 RRO。這項操作不會影響前景使用者 (userId = 10) 啟動的應用程式。如要為前景使用者啟用 RRO,請使用 -–user 10 參數:

adb shell cmd overlay enable --user 10 com.example.carrro

以程式輔助方式啟用或停用覆疊

使用 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 相同簽名的疊加層,都可以覆寫資源。系統設定中的 named-actor 標記會宣告演員。
  • config_signature。任何與 overlay-config apk 相同簽章的覆蓋圖層,都可以覆寫資源。系統設定中的 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 的新功能)。

偵錯重疊畫面

如要手動啟用、停用和傾印疊加層,請使用下列疊加層管理員殼層指令。

adb shell cmd overlay

使用 enable 但未指定使用者,會影響目前使用者,也就是擁有系統 UI 的系統使用者 (userId = 0)。這不會影響擁有應用程式的前景使用者 (userId = 10)。如要為前景使用者啟用 RRO,請使用 –-user 10 參數:

adb shell cmd overlay enable --user 10 com.example.carrro

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