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 một gói đích tại thời gian chạy. Ví dụ: một ứng dụng được cài đặt trên hình ảnh hệ thống có thể thay đổi hành vi dựa trên giá trị của một tài nguyên. Thay vì mã hoá cứng giá trị tài nguyên tại thời điểm tạo, một RRO được cài đặt trên một phân vùng khác có thể thay đổi 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 trạng thái bật/tắt theo phương thức lập trình để bật/tắt khả năng thay đổi giá trị tài nguyên của RRO. Theo mặc định, RRO bị tắt (tuy nhiên, RRO tĩnh được bật theo mặc định).
Tài nguyên lớp phủ
Lớp phủ hoạt động bằng cách liên kết các tài nguyên được xác định trong gói lớp phủ với các tài nguyên được xác định trong gói đích. Khi một ứng dụng cố gắng phân giải giá trị của một tài nguyên trong gói đích, giá trị của tài nguyên lớp phủ mà tài nguyên đích đượ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 gói đó chứa thẻ <overlay>
dưới dạng thẻ 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 đích mà RRO dự định phủ lên. Nếu đích đến không xác định một nhóm tài nguyên có thể phủ lên, thì bạn không nên sử dụng thuộc tính này.
Đoạn mã sau đây cho thấy một ví dụ về lớp phủ 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ủ lên mã nên không thể có tệp DEX. Ngoài ra, bạn phải đặt thuộc tính android:hasCode
của thẻ <application
> trong tệp kê khai thành false
.
Xác định bản đồ tài nguyên
Trong Android 11 trở lên, cơ chế được đề xuất để xác định bản đồ tài nguyên lớp phủ là tạo một tệp trong thư mục res/xml
của gói lớp phủ, liệt kê các tài nguyên mục tiêu cần được phủ và các giá trị thay thế của chúng, sau đó đặt giá trị của thuộc tính android:resourcesMap
của thẻ kê khai <overlay>
thành một tham chiếu đến tệp ánh xạ tài nguyên.
Đoạ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>
Đoạn mã sau đây cho thấy một ví dụ về tệp kê khai lớp phủ.
<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ợ một quy tắc bản dựng Soong cho các lớp phủ. Quy tắc này ngăn Công cụ đóng gói tài nguyên Android 2 (AAPT2) cố gắng loại bỏ các cấu hình trùng lặp của tài nguyên có cùng giá trị (--no-resource-deduping
) và ngăn việc xoá các tài nguyên không có cấu hình mặc định (--no-resource-removal
). Đoạn mã sau đây cho thấy một ví dụ về tệp Android.bp
.
runtime_resource_overlay {
name: "ExampleOverlay",
sdk_version: "current",
}
Giải quyết tài nguyên
Nếu một tài nguyên đích 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ì 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, hãy hợp nhất tập hợp cấu hình tài nguyên lớp phủ vào tập hợp cấu hình tài nguyên đích, sau đó làm theo quy trình phân giải tài nguyên thông thường (để biết thông tin chi tiết, hãy tham khảo phần Cách Android tìm ra tài nguyên phù hợp nhất).
Ví dụ: nếu một lớp phủ xác định giá trị cho cấu hình drawable-en
và mục tiêu xác định giá trị cho drawable-en-port
, thì drawable-en-port
sẽ có kết quả khớp tốt hơn nên giá trị của cấu hình mục tiêu drawable-en-port
sẽ được chọn trong thời gian chạy. Để phủ tất cả các cấu hình drawable-en
, lớp phủ phải xác định một giá trị cho mỗi cấu hình drawable-en
mà mục tiêu xác định.
Lớp phủ có thể tham chiếu các 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ó không gian mã nhận dạng tài nguyên dành riêng riêng biệt, không trùng lặp với 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 lớp phủ khác, do đó, các lớp phủ tham chiếu đến tài nguyên riêng sẽ hoạt động như mong đợi.
Trong Android 10 trở xuống, các lớp phủ và gói đích dùng chung cùng một không gian mã nhận dạng tài nguyên, điều này 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 mình bằng cú pháp
@type/name
.
Bật/tắt lớp phủ
Bạn có thể bật/tắt lớp phủ theo cách thủ công và theo phương thức lập trình.
Tắt hoặc bật lớp phủ theo cách thủ công
Để bật và xác minh RRO theo cách thủ công, hãy chạy:
adb shell cmd overlay enable --user current com.example.carrro
adb shell cmd overlay list --user current | grep -i com.example com.example.carrro
Việc này cho phép RRO cho người dùng hệ thống (userId = 0
) sở hữu SystemUI.
Hướng dẫn này không ảnh hưởng đến các ứng dụng do người dùng ở nền trước khởi động (userId = 10
). Để bật RRO cho người dùng ở nền trước, hãy sử dụng tham số -–user 10
:
adb shell cmd overlay enable --user 10 com.example.carrro
Bật hoặc tắt lớp phủ theo phương thức lập trình
Sử 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)
). Chỉ gói mà lớp phủ nhắm đến hoặc gói có quyền android.permission.CHANGE_OVERLAY_PACKAGES
mới có thể bật lớp phủ. Khi một lớp phủ được bật hoặc tắt, các sự kiện thay đổi cấu hình sẽ truyền đến gói đích và các hoạt động đích sẽ khởi chạy lại.
Hạn chế tài nguyên có thể phủ lên
Trong Android 10 trở lên, thẻ XML <overlayable>
sẽ hiển thị một bộ tài nguyên mà RRO được phép phủ lên. Trong tệp res/values/overlayable.xml
sau đây, string/foo
và integer/bar
là các tài nguyên được dùng để tạo giao diện cho thiết bị; để phủ các tài nguyên này, lớp phủ phải nhắm đến một cách rõ ràng tập hợp các 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 APK có thể xác định nhiều thẻ <overlayable>
, nhưng mỗi thẻ phải có một tên riêng biệt trong gói. Ví dụ:
Hai gói khác nhau đều có thể xác định
<overlayable name="foo">
.Không được phép để một APK có 2 khối
<overlayable name="foo">
.
Đoạn mã sau đây cho thấy ví dụ về một lớp phủ trong tệp 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 đến ứng dụng đó:
Bạn phải chỉ định
targetName
.Chỉ có thể phủ lên các 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 đến một gói hiển thị các tài nguyên có thể phủ lên nhưng không dùng android:targetName
để nhắm đến một thẻ <overlayable>
cụ thể.
Chính sách hạn chế
Sử dụng thẻ <policy>
để thực thi các hạn chế đối với tài nguyên có thể phủ lê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 được đưa vào. Sau đây là các loại được hỗ trợ.
public
. Mọi lớp phủ đều có thể ghi đè tài nguyên.system
. Mọi lớp phủ trên phân vùng hệ thống đều có thể ghi đè các tài nguyên.vendor
. Mọi lớp phủ trên phân vùng của nhà cung cấp đều có thể ghi đè các tài nguyên.product
. Mọi lớp phủ 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 chữ ký giống như 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 chữ ký giống như APK actor đều có thể ghi đè các tài nguyên. Tác nhân được khai báo trong thẻ named-actor trong cấu hình hệ thống.config_signature
. Mọi lớp phủ được ký bằng chữ ký giống như apk overlay-config đều có thể ghi đè các tài nguyên. overlay-config được 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 ví dụ về thẻ <policy>
trong tệp 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 dấu gạch đứng (|) làm ký tự phân cách.
Khi bạn chỉ định nhiều chính sách, lớp phủ chỉ cần đáp ứng một chính sách để ghi đè các tài nguyên được liệt kê 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 thay đổi, trạng thái mặc định và mức độ ưu tiên của lớp phủ, tuỳ thuộc vào 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ì các thuộc tính tệp kê khai. Bạn nên sử dụng tệp lớp phủ cho các lớp phủ.Tất cả các thiết bị đều có thể sử dụng các thuộc tính tệp kê khai (
android:isStatic
vàandroid: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 thay đổi, trạng thái mặc định và mức độ ưu tiên của lớp phủ. Để định cấu hình một lớp phủ, hãy 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ần định cấu hình. Để được định cấu hình, 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. Đoạn mã sau đây cho thấy một 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>
yêu cầu thuộc tính package
cho biết gói lớp phủ nào đang được định cấu hình. Thuộc tính enabled
không bắt buộc sẽ kiểm soát việc lớp phủ có được bật theo mặc định hay không (mặc định là false
). Thuộc tính mutable
không bắt buộc sẽ kiểm soát việc lớp phủ có thể thay đổi hay không và có thể thay đổi trạng thái đã bật theo cách lập trình trong thời gian chạy hay không (mặc định là true
). Các lớp phủ không có trong tệp cấu hình có thể thay đổi và bị tắt theo mặc định.
Mức độ ưu tiên của lớp phủ
Khi nhiều lớp phủ ghi đè cùng một tài nguyên, thứ tự của các lớp phủ là rất quan trọng. Lớp phủ có mức độ ưu tiên cao hơn các lớp phủ có cấu hình trước cấu hình của chính lớp phủ đó. Sau đây là thứ tự ưu tiên của lớp phủ trong các phân vùng khác nhau (từ thứ tự ưu tiên thấp nhất đến cao nhất).
system
vendor
odm
oem
product
system_ext
Hợp nhất 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 tại vị trí được chỉ định vào tệp cấu hình. Thuộc tính path
của thẻ này biểu thị đường dẫn của tệp cần hợp nhất so với thư mục chứa các tệp cấu hình lớp phủ.
Sử dụng các thuộc tính tệp kê khai/RRO tĩnh
Trong Android 10 trở xuống, tính bất biến và thứ tự ưu tiên của lớp phủ được định cấu hình bằ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ànhtrue
, lớp phủ sẽ được bật theo mặc định và không thể thay đổi. Điều này ngăn lớp phủ bị tắt.android:priority
. Giá trị của thuộc tính dạng số này (chỉ ảnh hưởng đến lớp phủ tĩnh) sẽ định cấu hình mức độ ưu tiên của lớp phủ khi nhiều lớp phủ tĩnh nhắm đến cùng một giá trị tài nguyên. Số càng cao thì mức độ ưu tiên càng cao.
Đoạn mã sau đây cho thấy một 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 nằm trong partition/overlay/config/config.xml
, các lớp phủ sẽ được định cấu hình bằng tệp đó và android:isStatic
cũng như android:priority
sẽ không ảnh hưởng đến các lớp phủ nằm trong phân vùng. Việc xác định tệp cấu hình lớp phủ trong bất kỳ phân vùng nào sẽ thực thi mức độ ưu tiên của 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 hưởng đến giá trị của các tài nguyên được đọc trong quá trình cài đặt gói. Đối với 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 các boolean định cấu hình trạng thái đã bật của thành phần, hãy sử dụng thẻ <component-override>
SystemConfig
(mới trong Android 11).
Lớp phủ gỡ lỗi
Để bật, tắt và kết xuất lớp phủ theo cách thủ công, hãy dùng lệnh shell của trình quản lý lớp phủ sau.
adb shell cmd overlay
Việc sử dụng enable
mà không chỉ định người dùng sẽ ảnh hưởng đến người dùng hiện tại, tức là người dùng hệ thống (userId = 0
), người sở hữu Giao diện người dùng hệ thống. Điều này không ảnh hưởng đến người dùng ở nền trước (userId = 10
), là người sở hữu các ứng dụng. Để bật RRO cho người dùng trên nền trước, hãy sử dụng tham số –-user 10
:
adb shell cmd overlay enable --user 10 com.example.carrro
OverlayManagerService
dùng idmap2
để liên kết mã nhận dạng tài nguyên trong gói đích với mã nhận dạng tài nguyên trong gói lớp phủ. Các mối liên kết mã nhận dạng được tạo sẽ được lưu trữ trong /data/resource-cache/
. Nếu lớp phủ của bạn không hoạt động đúng cách, hãy tìm tệp idmap
tương ứng cho lớp phủ của bạn trong /data/resource-cache/
, sau đó chạy lệnh sau.
adb shell idmap2 dump --idmap-path [file]
Lệnh này in ra mối liên kết của các tài nguyên như minh hoạ bên dưới.
[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType