在執行階段變更應用程式的值('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. Any overlay on the oem partition can override the resources.
  • odm。odm 分區上的任何疊加層都可以覆寫資源。
  • signature. 任何使用與目標 APK 相同簽章簽署的疊加層,都可以覆寫資源。
  • 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