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 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 cài đặt trên hình ảnh hệ thống có thể thay đổi hành vi của ứng dụng đó 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 tạo bản dựng, 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 của RRO trong việc thay đổi giá trị tài nguyên. 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 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 đích, giá trị của tài nguyên lớp phủ mà tài nguyên đích được liên kết 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ủ của gói mục tiêu mà RRO dự định phủ lên. Nếu mục tiêu không xác định một tập hợp tài nguyên có thể phủ, thì thuộc tính này sẽ không xuất hiện.

Mã sau đây cho thấy một lớp phủ AndroidManifest.xml 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"/>
</manifest>

Lớp phủ không thể phủ mã, vì vậy, lớp phủ 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ủ lên và giá trị thay thế của các tài nguyên đó, sau đó đặt giá trị của thuộc tính android:resourcesMap của thẻ kê khai <overlay> thành tham chiếu đến 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 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) cố gắng loại bỏ trùng lặp cấu hình của các tài nguyên có cùng giá trị (--no-resource-deduping) và xoá các tài nguyên không có cấu hình mặc định (--no-resource-removal). Mã sau đây cho thấy một tệp Android.bp mẫu.

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

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

Nếu một 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ì 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 là cấu hình phù hợp nhất, hãy hợp nhất nhóm cấu hình tài nguyên lớp phủ vào nhóm cấu hình tài nguyên mục tiêu, 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 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ẽ 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ấ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 tài nguyên của riêng lớp phủ, 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 riêng được đặt trước, 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. Vì vậy, các lớp phủ tham chiếu đến tài nguyên của riêng chúng sẽ hoạt động như mong đợi.

  • Trong Android 10 trở xuống, lớp phủ và gói mục tiêu dùng chung 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 các lớp phủ và gói mục tiêu 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ủ

Bạn có thể bật/tắt lớp phủ theo cách thủ công và theo 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

Thao tác này sẽ bật 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 trên nền trước khởi động (userId = 10). Để bật RRO cho người dùng trên nền trước, hãy sử dụng thông 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)). Lớp phủ chỉ có thể được bật bằng gói mà lớp phủ nhắm đến hoặc bằng gói có quyền android.permission.CHANGE_OVERLAY_PACKAGES. 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 mục tiêu và các hoạt động mục tiêu sẽ 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 tập hợp tài nguyên mà RRO được phép phủ lên. Trong tệp res/values/overlayable.xml ví dụ sau, string/foointeger/bar là các tài nguyên 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 mục tiêu rõ ràng đến 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 tệp APK có thể xác định nhiều thẻ <overlayable>, nhưng mỗi thẻ phải có một tên duy nhất trong gói. Ví dụ:

  • Cho phép hai gói khác nhau xác định <overlayable name="foo">.

  • Một tệp APK không được có hai khối <overlayable name="foo">.

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

  • Phải chỉ định targetName.

  • Chỉ có thể phủ 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ủ nhưng không sử 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 quy định hạn chế đối với tài nguyên có thể phủ. 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 đè 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 đè tài nguyên.
  • product. Mọi lớp phủ trên phân vùng sản phẩm đều có thể ghi đè 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 một 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 một chữ ký với APK thực thể đều có thể ghi đè các tài nguyên. Thành phần này đượ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 cùng một chữ ký với tệp APK overlay-config đều có thể ghi đè các tài nguyên. Cấu hình lớp phủ được khai báo trong thẻ overlay-config-signature trong cấu hình hệ thống.

Mã sau đây cho thấy một thẻ <policy> mẫu 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 thực hiện 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 lớp phủ.

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

Sử dụng OverlayConfig

Trên Android 11 trở lên, bạn có thể sử 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 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. Mã sau đây cho thấy một product/overlay/config/config.xml mẫu.

<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 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 kiểm soát việc lớp phủ có thể thay đổi và có thể thay đổi trạng thái bật theo phương thức lập trình trong thời gian chạy hay không (mặc định là true). Theo mặc định, các lớp phủ không có trong tệp cấu hình sẽ có thể thay đổi và bị tắt.

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 so với các lớp phủ có cấu hình trước cấu hình của chính lớp phủ đó. Thứ tự ưu tiên của lớp phủ trong các phân vùng khác nhau (từ thấp nhất đến cao nhất) như sau.

  • 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ẻ đại diện cho đườ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 thuộc tính tệp kê khai/RRO tĩnh

Trên Android 10 trở xuống, tính chất không thể thay đổi và mức độ ư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ành true, lớp phủ sẽ được bật theo mặc định và không thể thay đổi, nhờ đó lớp phủ sẽ không 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.

Mã sau đây cho thấy một AndroidManifest.xml 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: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, thì các lớp phủ sẽ được định cấu hình bằng tệp đó và android:isStaticandroid: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 thứ tự ưu tiên của phân vùng lớp phủ.

Ngoài ra, Android 11 trở lên sẽ xoá khả năng sử dụng lớp phủ tĩnh để ảnh hưởng đến các giá trị của 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 boolean định cấu hình trạng thái bật 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 sử 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) 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 trên nền trước (userId = 10) 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 thông số –-user 10:

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

OverlayManagerService sử dụng idmap2 để liên kết mã nhận dạng tài nguyên trong gói mục tiêu 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ủ trong /data/resource-cache/, sau đó chạy lệnh sau.

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

Lệnh này sẽ in bản đồ ánh xạ 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