Thay đổi giá trị của tài nguyên của ứng dụng trong thời gian chạy

Lớp phủ tài nguyên thời gian chạy (RRO) là một gói thay đổi các giá trị tài nguyên của gói mục tiêu trong thời gian chạy. Ví dụ: một ứng dụng đã cài đặt trên hệ thống hình ảnh có thể thay đổi hành vi dựa trên giá trị của tài nguyên. Thay vì mã hoá cứng giá trị tài nguyên tại thời điểm xây dựng, một RRO được cài đặt trên phân vùng có thể thay đổi các giá trị của tài nguyên ứng dụng trong thời gian chạy.

Bạn có thể bật hoặc tắt RRO. Bạn có thể đặt giá trị trạng thái bật/tắt để bật/tắt khả năng thay đổi giá trị tài nguyên của RRO. RRO đều bị tắt theo mặc định (tuy nhiên, RRO tĩnh được bật bằng mặc định).

Tài nguyên lớp phủ

Lớp phủ hoạt động bằng cách ánh xạ tài nguyên được xác định trong gói lớp phủ đến các tài nguyên được xác định trong gói mục tiêu. Khi một ứng dụng cố gắng phân giải giá trị của tài nguyên trong gói mục tiêu, giá trị của tài nguyên lớp phủ mà mục tiêu thay vào đó, tài nguyên được ánh xạ đến sẽ được trả về.

Thiết lập tệp kê khai

Một gói được coi là gói RRO nếu chứa thẻ <overlay> dưới dạng một con của thẻ <manifest>.

  • Giá trị của thuộc tính android:targetPackage bắt buộc chỉ định tên của gói mà RRO dự định phủ lên.

  • Giá trị của thuộc tính android:targetName (không bắt buộc) chỉ định tên của tập hợp con tài nguyên có thể phủ lên của gói mục tiêu mà RRO dự định lớp phủ. Nếu mục tiêu không xác định nhóm tài nguyên có thể phủ, không nên tồn tại.

Mã sau đây cho thấy một lớp phủ mẫu 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>

Lớp phủ không thể phủ mã, vì vậy, chúng không thể có tệp DEX. Ngoài ra, Thuộc tính android:hasCode của <application> trong tệp kê khai phải được đặt thành false.

Xác định bản đồ tài nguyên

Trên Android 11 trở lên, cơ chế được đề xuất để việc xác định bản đồ tài nguyên lớp phủ là tạo một tệp trong res/xml thư mục của gói lớp phủ, liệt kê các tài nguyên mục tiêu cần được và các giá trị thay thế của chúng, sau đó đặt giá trị của lớp Thuộc tính android:resourcesMap của thẻ tệp kê khai <overlay> đến một tham chiếu vào tệp ánh xạ tài nguyên.

Mã sau đây cho thấy một tệp res/xml/overlays.xml mẫu.

<?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>

Mã sau đây cho thấy một tệp kê khai lớp phủ mẫu.

<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>

Tạo gói

Android 11 trở lên hỗ trợ quy tắc bản dựng Soong cho lớp phủ ngăn Công cụ đóng gói tài nguyên Android 2 (AAPT2) tìm cách cấu hình trùng lặp của các tài nguyên có cùng giá trị (--no-resource-deduping) và xoá tài nguyên không có chế độ mặc định cấu hình (--no-resource-removal). Mã sau đây cho thấy một ví dụ Android.bp.

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

Giải quyết tài nguyên

Nếu tài nguyên mục tiêu hoặc tài nguyên lớp phủ có nhiều cấu hình được xác định cho tài nguyên đang được truy vấn, thời gian chạy tài nguyên sẽ trả về giá trị của cấu hình phù hợp nhất với cấu hình của cấu hình thiết bị. Để xác định cấu hình nào phù hợp nhất với cấu hình, hãy hợp nhất tập hợp các cấu hình tài nguyên lớp phủ thành tập hợp tài nguyên mục tiêu rồi tuân theo quy trình phân giải tài nguyên thông thường (đối với chi tiết, hãy tham khảo Cách Android tìm ra ứng dụng phù hợp nhất ).

Ví dụ: nếu lớp phủ xác định giá trị cho cấu hình drawable-en và mục tiêu sẽ xác định giá trị cho drawable-en-port, drawable-en-port có kết quả phù hợp hơn, vì vậy, giá trị của cấu hình mục tiêu drawable-en-port sẽ được chọn khi chạy. Để phủ tất cả cấu hình drawable-en, lớp phủ phải xác định giá trị cho từng cấu hình drawable-en mà mục tiêu xác định.

Lớp phủ có thể tham chiếu tài nguyên riêng với các hành vi khác nhau giữa Các bản phát hành Android.

  • Trong Android 11 trở lên, mỗi lớp phủ đều có riêng không gian mã nhận dạng tài nguyên dành riêng không chồng chéo lên không gian mã nhận dạng tài nguyên mục tiêu hoặc các không gian mã nhận dạng tài nguyên của lớp phủ khác, vì vậy, các lớp phủ tham chiếu đến tài nguyên của chúng hoạt động như dự kiến.

  • Trong Android 10 trở xuống, lớp phủ và gói mục tiêu dùng chung một tài nguyên Không gian mã nhận dạng, có thể gây ra xung đột và hành vi không mong muốn khi chúng cố gắng để tham chiếu tài nguyên của riêng chúng bằng cú pháp @type/name.

Bật/tắt lớp phủ

Dùng API OverlayManager để bật và tắt lớp phủ có thể thay đổi (truy xuất giao diện API bằng Context#getSystemService(Context.OVERLAY_SERVICE)). Một chỉ có thể bật lớp phủ bằng gói mà nó nhắm mục tiêu hoặc bởi một gói có Quyền android.permission.CHANGE_OVERLAY_PACKAGES. Khi lớp phủ bật hoặc tắt, các sự kiện thay đổi cấu hình sẽ truyền đến gói mục tiêu và hoạt động mục tiêu khởi chạy lại.

Hạn chế tài nguyên có thể phủ

Trong Android 10 trở lên, thẻ XML <overlayable> hiển thị một nhóm tài nguyên mà RRO được phép chồng lên. Trong ví dụ sau đây Tệp res/values/overlayable.xml, string/foointeger/bar là các tài nguyên dùng để tuỳ chỉnh giao diện của thiết bị; để phủ các tài nguyên này, phải nhắm mục tiêu rõ ràng tập hợp tài nguyên có thể phủ theo tên.

<!-- 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>

Một tệp APK có thể xác định nhiều thẻ <overlayable>, nhưng mỗi thẻ phải có một duy nhất tên trong gói. Ví dụ:

  • Có thể sử dụng hai gói khác nhau để cùng xác định <overlayable name="foo">.

  • Không chấp nhận được với một APK có hai khối <overlayable name="foo">.

Đoạn mã sau đây cho thấy ví dụ về một lớp phủ trong 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>

Khi một ứng dụng xác định thẻ <overlayable>, các lớp phủ nhắm mục tiêu đến ứng dụng đó:

  • Phải chỉ định targetName.

  • Chỉ có thể phủ những tài nguyên được liệt kê trong thẻ <overlayable>.

  • Chỉ có thể nhắm đến một tên <overlayable>.

Bạn không thể bật lớp phủ nhắm mục tiêu một gói có thể hiển thị lớp phủ nhưng không sử dụng android:targetName để nhắm mục tiêu một Thẻ <overlayable>.

Hạn chế chính sách

Dùng thẻ <policy> để thực thi các quy định hạn chế đối với tài nguyên có thể phủ. Chiến lược phát hành đĩa đơn Thuộc tính type chỉ định những chính sách mà lớp phủ phải đáp ứng để ghi đè các tài nguyên đi kèm. Sau đây là các loại được hỗ trợ.

  • public. Bất kỳ lớp phủ nào cũng có thể ghi đè tài nguyên.
  • system. Bất kỳ lớp phủ nào trên phân vùng hệ thống đều có thể ghi đè các tài nguyên.
  • vendor. Bất kỳ lớp phủ nào trên phân vùng nhà cung cấp đều có thể ghi đè các tài nguyên.
  • product. Bất kỳ lớp phủ nào trên phân vùng sản phẩm đều có thể ghi đè các tài nguyên.
  • oem. Mọi lớp phủ trên phân vùng oem đều có thể ghi đè các tài nguyên.
  • odm. Mọi lớp phủ trên phân vùng odm đều có thể ghi đè các tài nguyên.
  • signature. Mọi lớp phủ được ký bằng cùng chữ ký với APK mục tiêu đều có thể ghi đè các tài nguyên.
  • actor. Mọi lớp phủ được ký bằng cùng chữ ký với APK actor đều có thể ghi đè các tài nguyên. Tác nhân này được khai báo trong thẻ firstname-actor trong hệ thống config của Google.
  • config_signature. Bất kỳ lớp phủ nào được ký bằng cùng chữ ký với APK overlay-config có thể ghi đè các tài nguyên. Cấu hình lớp phủ là đã khai báo trong thẻ overlay-config-signature trong cấu hình hệ thống.

Đoạn mã sau đây cho thấy một thẻ <policy> mẫu trong 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>

Để chỉ định nhiều chính sách, hãy sử dụng thanh dọc (|) làm ký tự phân tách. Khi bạn chỉ định nhiều chính sách, một lớp phủ chỉ cần đáp ứng một chính sách để ghi đè các tài nguyên có trong thẻ <policy>.

Định cấu hình lớp phủ

Android hỗ trợ nhiều cơ chế để định cấu hình khả năng biến đổi, trạng thái và mức độ ưu tiên của lớp phủ tuỳ theo phiên bản phát hành Android.

  • Các thiết bị chạy Android 11 trở lên có thể sử dụng tệp OverlayConfig (config.xml) thay vì thuộc tính tệp kê khai. Sử dụng tệp lớp phủ là phương pháp được đề xuất cho lớp phủ.

  • Mọi thiết bị đều có thể sử dụng thuộc tính tệp kê khai (android:isStaticandroid:priority) để định cấu hình RRO tĩnh.

Sử dụng OverlayConfig

Trong Android 11 trở lên, bạn có thể dùng OverlayConfig để định cấu hình khả năng biến đổi, trạng thái mặc định và mức độ ưu tiên của lớp phủ. Để định cấu hình lớp phủ, tạo hoặc sửa đổi tệp nằm tại partition/overlay/config/config.xml, trong đó partition là phân vùng của lớp phủ được định cấu hình. Để được định cấu hình, một lớp phủ phải nằm trong Thư mục overlay/ của phân vùng mà lớp phủ được định cấu hình. Chiến lược phát hành đĩa đơn đoạn mã sau đây cho thấy ví dụ về 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>"

Thẻ <overlay> cần có thuộc tính package cho biết lớp phủ nào gói đang được định cấu hình. Thuộc tính enabled (không bắt buộc) kiểm soát việc hoặc lớp phủ không được bật theo mặc định (mặc định là false). Trường không bắt buộc Thuộc tính mutable kiểm soát việc lớp phủ có thể thay đổi và có thể có hay không Trạng thái bật của thiết bị đã thay đổi theo cách lập trình trong thời gian chạy (mặc định là true). Lớp phủ không được liệt kê trong tệp cấu hình có thể thay đổi và tắt bởi mặc định.

Mức độ ưu tiên của lớp phủ

Khi nhiều lớp phủ ghi đè lên cùng một tài nguyên thì thứ tự của các lớp phủ là rất quan trọng. Lớp phủ có mức độ ưu tiên lớn hơn lớp phủ có cấu hình trước khi cấu hình của chính nó. Thứ tự ưu tiên của lớp phủ trong các phân vùng (từ mức độ ưu tiên ít nhất đến lớn nhất) như sau.

  • system
  • vendor
  • odm
  • oem
  • product
  • system_ext

Hợp nhất các tệp

Việc sử dụng thẻ <merge> cho phép hợp nhất các tệp cấu hình khác ở vị trí được chỉ định vào tệp cấu hình. Thuộc tính path của thẻ biểu thị đường dẫn của tệp cần hợp nhất tương ứng với thư mục chứa tệp cấu hình lớp phủ.

Sử dụng thuộc tính tệp kê khai/RRO tĩnh

Trong Android 10 trở xuống, tính bất biến và mức độ ưu tiên của lớp phủ được định cấu hình bằng cách sử dụng các thuộc tính tệp kê khai sau.

  • android:isStatic. Khi giá trị của thuộc tính boolean này được đặt thành true, lớp phủ được bật theo mặc định và không thể thay đổi, giúp ngăn chặn lớp phủ không bị vô hiệu hoá.

  • android:priority. Giá trị của thuộc tính số này (chỉ ảnh hưởng đến lớp phủ tĩnh) định cấu hình mức độ ưu tiên của lớp phủ khi có nhiều lớp phủ tĩnh lớp phủ nhắm mục tiêu cùng một giá trị tài nguyên. Chỉ số càng cao thì giá trị càng cao quyền ưu tiên.

Mã sau đây cho thấy ví dụ về 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>

Các thay đổi trong Android 11

Trong Android 11 trở lên, nếu tệp cấu hình được nằm trong partition/overlay/config/config.xml, lớp phủ được định cấu hình bằng cách sử dụng tệp đó, android:isStaticandroid:priority không có ảnh hưởng đến nằm trong phân vùng. Xác định tệp cấu hình lớp phủ trong bất kỳ phân vùng sẽ thực thi mức độ ưu tiên phân vùng lớp phủ.

Ngoài ra, Android 11 trở lên sẽ loại bỏ khả năng để sử dụng lớp phủ tĩnh nhằm tác động đến giá trị của các tài nguyên được đọc trong gói cài đặt. Trong trường hợp sử dụng phổ biến là sử dụng lớp phủ tĩnh để thay đổi giá trị của boolean định cấu hình trạng thái kích hoạt của thành phần, hãy sử dụng Thẻ SystemConfig <component-override> (mới trong Android 11).

Gỡ lỗi lớp phủ

Để bật, tắt và kết xuất lớp phủ theo cách thủ công, hãy sử dụng lớp phủ sau lệnh shell của trình quản lý.

adb shell cmd overlay

OverlayManagerService sử dụng idmap2 để liên kết mã tài nguyên trong đích cho mã nhận dạng tài nguyên trong gói lớp phủ. Các liên kết mã nhận dạng đã tạo đang được lưu trữ trong /data/resource-cache/. Nếu lớp phủ của bạn không hoạt động chính xác, hãy tìm tệp idmap tương ứng cho lớp phủ trong /data/resource-cache/, sau đó chạy lệnh sau.

adb shell idmap2 dump --idmap-path [file]

Lệnh này in ánh xạ các tài nguyên như được hiển thị dưới đây.

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType