เปลี่ยนค่าทรัพยากรของแอปขณะรันไทม์

การซ้อนทับทรัพยากรรันไทม์ (RRO) คือแพ็กเกจที่เปลี่ยนค่าทรัพยากรของแพ็กเกจเป้าหมายขณะรันไทม์ เช่น แอปที่ติดตั้งในระบบอาจเปลี่ยนลักษณะการทํางานตามค่าของทรัพยากร RRO ที่ติดตั้งในพาร์ติชันอื่นสามารถเปลี่ยนค่าของทรัพยากรของแอปขณะรันไทม์ได้ แทนที่จะกำหนดค่าทรัพยากรแบบฮาร์ดโค้ดไว้เมื่อสร้าง

คุณเปิดหรือปิดใช้ RRO ได้ คุณสามารถตั้งค่าสถานะเปิด/ปิดแบบเป็นโปรแกรมเพื่อสลับความสามารถของ RRO ในการเปลี่ยนค่าทรัพยากร ระบบจะปิดใช้ RRO โดยค่าเริ่มต้น (แต่ระบบจะเปิดใช้ RRO แบบคงที่โดยค่าเริ่มต้น)

ทรัพยากรการวางซ้อน

การวางซ้อนจะทํางานโดยการแมปทรัพยากรที่กําหนดไว้ในแพ็กเกจการวางซ้อนกับทรัพยากรที่กําหนดไว้ในแพ็กเกจเป้าหมาย เมื่อแอปพยายามแก้ไขค่าของทรัพยากรในแพ็กเกจเป้าหมาย ระบบจะแสดงค่าของทรัพยากรการวางซ้อนที่จับคู่กับทรัพยากรเป้าหมายแทน

ตั้งค่าไฟล์ Manifest

ระบบจะถือว่าแพ็กเกจเป็นแพ็กเกจ RRO หากมีแท็ก <overlay> เป็นแท็กย่อยของแท็ก <manifest>

  • ค่าของแอตทริบิวต์ 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 นอกจากนี้ แอตทริบิวต์ android:hasCode ของแท็ก <application> ในไฟล์ Manifest ต้องตั้งค่าเป็น false

กำหนดแผนที่ทรัพยากร

ใน Android 11 ขึ้นไป กลไกที่แนะนําในการกําหนดแผนที่ทรัพยากรการวางซ้อนคือสร้างไฟล์ในไดเรกทอรี res/xml ของแพ็กเกจการวางซ้อน ระบุทรัพยากรเป้าหมายที่ควรวางซ้อนและค่าที่แทนที่ จากนั้นตั้งค่าแอตทริบิวต์ android:resourcesMap ของแท็ก Manifest <overlay> เป็นข้อมูลอ้างอิงไปยังไฟล์การแมปทรัพยากร

โค้ดต่อไปนี้แสดงตัวอย่างไฟล์ 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 การวางซ้อน

<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 Asset Packaging Tool 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 ขึ้นไป การวางซ้อนแต่ละรายการจะมีพื้นที่รหัสทรัพยากรที่สงวนไว้ของตนเอง ซึ่งจะไม่ทับซ้อนกับพื้นที่รหัสทรัพยากรเป้าหมายหรือพื้นที่รหัสทรัพยากรของการวางซ้อนอื่นๆ เพื่อให้การวางซ้อนที่อ้างอิงทรัพยากรของตนเองทำงานได้ตามที่คาดไว้

  • ใน Android 10 หรือต่ำกว่า การวางซ้อนและแพ็กเกจเป้าหมายจะใช้พื้นที่รหัสทรัพยากรเดียวกัน ซึ่งอาจทำให้เกิดข้อขัดแย้งและลักษณะการทำงานที่ไม่คาดคิดเมื่อพยายามอ้างอิงทรัพยากรของตนเองโดยใช้ไวยากรณ์ @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

ซึ่งจะเปิดใช้ RRO สําหรับผู้ใช้ระบบ (userId = 0) ที่เป็นเจ้าของ SystemUI คำสั่งนี้จะไม่ส่งผลต่อแอปที่ผู้ใช้ที่ทำงานอยู่เบื้องหน้าเริ่มต้น (userId = 10) หากต้องการเปิดใช้ RRO สำหรับผู้ใช้ที่ทำงานอยู่เบื้องหน้า ให้ใช้พารามิเตอร์ -–user 10 ดังนี้

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

เปิดหรือปิดใช้การวางซ้อนแบบเป็นโปรแกรม

ใช้ OverlayManager API เพื่อเปิดและปิดใช้การวางซ้อนที่เปลี่ยนแปลงได้ (เรียกข้อมูลอินเทอร์เฟซ API โดยใช้ Context#getSystemService(Context.OVERLAY_SERVICE)) แพ็กเกจที่กำหนดเป้าหมายหรือแพ็กเกจที่มีสิทธิ์ android.permission.CHANGE_OVERLAY_PACKAGES เท่านั้นที่เปิดใช้การวางซ้อนได้ เมื่อเปิดหรือปิดใช้การวางซ้อน เหตุการณ์การเปลี่ยนแปลงการกําหนดค่าจะเผยแพร่ไปยังแพ็กเกจเป้าหมายและกิจกรรมเป้าหมายจะเปิดใช้งานอีกครั้ง

จำกัดทรัพยากรที่วางซ้อนได้

ใน Android 10 ขึ้นไป แท็ก <overlayable> XML จะแสดงชุดทรัพยากรที่ RRO วางซ้อนได้ ในตัวอย่างต่อไปนี้ ไฟล์ res/values/overlayable.xml, string/foo และ integer/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> ได้หลายแท็ก แต่แต่ละแท็กต้องมีชื่อที่ไม่ซ้ำกันภายในแพ็กเกจ เช่น

  • แพ็กเกจ 2 รายการที่แตกต่างกันสามารถกำหนด <overlayable name="foo"> ได้

  • APK รายการเดียวต้องมีบล็อก <overlayable name="foo"> 2 บล็อก

โค้ดต่อไปนี้แสดงตัวอย่างการวางซ้อนใน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 สามารถลบล้างทรัพยากรได้ ประกาศ Actor ในแท็ก named-actor ใน system config
  • config_signature. การวางซ้อนที่รับรองด้วยลายเซ็นเดียวกับ APK overlay-config จะลบล้างทรัพยากรได้ มีการประกาศ overlay-config ในแท็ก overlay-config-signature ในการกำหนดค่าระบบ

โค้ดต่อไปนี้แสดงตัวอย่างแท็ก <policy> ในไฟล์ res/values/overlayable.xml

<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) แทนแอตทริบิวต์ไฟล์ Manifest ได้ เราขอแนะนำให้ใช้ไฟล์วางซ้อนสำหรับวางซ้อน

  • อุปกรณ์ทุกเครื่องสามารถใช้แอตทริบิวต์ไฟล์ Manifest (android:isStatic และ android: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 ของแท็กแสดงถึงเส้นทางของไฟล์ที่จะผสานซึ่งสัมพันธ์กับไดเรกทอรีที่มีไฟล์การกําหนดค่าการวางซ้อน

ใช้แอตทริบิวต์ไฟล์ Manifest/RRO แบบคงที่

ใน Android 10 หรือต่ำกว่า ระบบจะกำหนดค่าการคงที่และการมีลําดับความสําคัญของการวางซ้อนโดยใช้แอตทริบิวต์ไฟล์ Manifest ต่อไปนี้

  • 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:isStatic และ android:priority จะไม่มีผลกับการวางซ้อนที่อยู่ในพาร์ติชัน การกําหนดไฟล์การกําหนดค่าการวางซ้อนในพาร์ติชันใดก็ตามจะบังคับใช้ลําดับความสําคัญของพาร์ติชันการวางซ้อน

นอกจากนี้ Android 11 ขึ้นไปจะนําความสามารถในการใช้การวางซ้อนแบบคงที่เพื่อส่งผลต่อค่าของทรัพยากรที่อ่านระหว่างการติดตั้งแพ็กเกจออก สําหรับกรณีการใช้งานทั่วไปในการใช้การวางซ้อนแบบคงที่เพื่อเปลี่ยนค่าบูลีนที่กำหนดค่าสถานะเปิดใช้คอมโพเนนต์ ให้ใช้แท็ก <component-override> SystemConfig (ใหม่ใน Android 11)

หน้าจอแก้ไขข้อบกพร่องแบบวางซ้อน

หากต้องการเปิดใช้ ปิดใช้ และถ่ายโอนข้อมูลการวางซ้อนด้วยตนเอง ให้ใช้คำสั่งเชลล์ของผู้จัดการการวางซ้อนต่อไปนี้

adb shell cmd overlay

การใช้ enable โดยไม่ระบุผู้ใช้จะมีผลกับผู้ใช้ปัจจุบัน ซึ่งก็คือผู้ใช้ระบบ (userId = 0) ที่เป็นเจ้าของ UI ของระบบ การดำเนินการนี้จะไม่ส่งผลต่อผู้ใช้ที่ทำงานอยู่เบื้องหน้า (userId = 10) ซึ่งเป็นเจ้าของแอป หากต้องการเปิดใช้ RRO สําหรับผู้ใช้ที่ทำงานอยู่เบื้องหน้า ให้ใช้พารามิเตอร์ –-user 10 ดังนี้

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

OverlayManagerService ใช้ idmap2 เพื่อจับคู่รหัสทรัพยากรในแพ็กเกจเป้าหมายกับรหัสทรัพยากรในแพ็กเกจการวางซ้อน ระบบจะจัดเก็บการจับคู่รหัสที่สร้างขึ้นไว้ใน /data/resource-cache/ หากการวางซ้อนไม่ทํางานอย่างถูกต้อง ให้ค้นหาไฟล์ idmap ที่สอดคล้องกันสําหรับการวางซ้อนใน /data/resource-cache/ แล้วเรียกใช้คําสั่งต่อไปนี้

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