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

การซ้อนทับทรัพยากรรันไทม์ (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

เปิด/ปิดใช้การวางซ้อน

ใช้ 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 สามารถลบล้างทรัพยากรได้ มีการประกาศผู้ดำเนินการในแท็ก named-actor ในการกำหนดค่าระบบ
  • config_signature โฆษณาซ้อนทับที่ลงนามด้วยลายเซ็นเดียวกันกับ APK 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

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