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

Lớp phủ tài nguyên thời gian chạy (RRO) là gói thay đổi giá trị tài nguyên của gói đích khi 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 nó dựa trên giá trị của tài nguyên. Thay vì mã hóa cứng giá trị tài nguyên tại thời điểm xây dựng, RRO được cài đặt trên một phân vùng khác có thể thay đổi giá trị tài nguyên của ứng dụng khi chạy.

RRO có thể được kích hoạt hoặc vô hiệu hóa. Bạn có thể lập trình đặt trạng thái bật/tắt để chuyển đổi khả năng thay đổi giá trị tài nguyên của RRO. RRO bị tắt theo mặc định (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 ánh xạ các tài nguyên được xác định trong gói lớp phủ tớ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 tài nguyên trong gói mục tiêu, thay vào đó, giá trị của tài nguyên lớp phủ mà tài nguyên đích được ánh xạ tới sẽ được trả về.

Thiết lập bảng kê khai

Một gói được coi là gói RRO nếu nó chứa thẻ <overlay> là phần tử con của thẻ <manifest> .

  • Giá trị của thuộc tính android:targetPackage được yêu cầu 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 tùy chọn chỉ định tên của tập hợp con tài nguyên có thể xếp chồng của gói mục tiêu mà RRO dự định xếp chồng. Nếu mục tiêu không xác định tập hợp tài nguyên có thể phủ lên thì thuộc tính này sẽ không xuất hiện.

Đoạn mã sau đây hiển thị một lớp phủ ví dụ 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ã nên chúng không thể có tệp DEX. Ngoài ra, thuộc tính android:hasCode của thẻ <application > trong tệp kê khai phải được đặt thành false .

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

Trong Android 11 trở lên, cơ chế xác định bản đồ tài nguyên lớp phủ được đề xuất 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 đích cần được phủ và các giá trị thay thế của chúng, sau đó đặt giá trị của Thuộc android:resourcesMap của thẻ kê khai <overlay> để tham chiếu đến tệp ánh xạ tài nguyên.

Đoạn mã sau đây hiển thị một ví dụ về tệp 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>

Mã sau đây hiển thị một bảng 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>

Xây dựng gói

Android 11 trở lên hỗ trợ quy tắc xây dựng Soong cho các lớp phủ ngăn Công cụ đóng gói tài sản Android 2 (AAPT2) cố gắng loại bỏ các cấu hình tài nguyên có cùng giá trị ( --no-resource-deduping ) và xóa các tài nguyên không có cấu hình mặc định ( --no-resource-removal ). Đoạn mã sau đây hiển thị 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 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 là cấu hình 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 đó thực hiện theo quy trình phân giải tài nguyên thông thường (để biết chi tiết, hãy tham khảo Cách Android tìm tài nguyên 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 xác định giá trị cho drawable-en-port thì drawable-en-port có giá trị khớp tốt hơn nên giá trị của cấu hình đích drawable-en-port là được chọn trong thời gian chạy. Để xếp chồng 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 tài nguyên của riêng chú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ủ có không gian ID tài nguyên dành riêng, không chồng lên không gian ID tài nguyên mục tiêu hoặc các không gian ID tài nguyên lớp phủ khác, do đó, các lớp phủ tham chiếu tài nguyên của chính chúng sẽ hoạt động như mong đợi.

  • Trong Android 10 trở xuống, các lớp phủ và gói mục tiêu chia sẻ cùng một không gian ID 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 chính mình bằng cú @type/name .

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

Sử dụng API OverlayManager để bật và tắt các lớp phủ có thể thay đổi (truy xuất giao diện API bằng cách sử dụng Context#getSystemService(Context.OVERLAY_SERVICE) ). Lớp phủ chỉ có thể được bật bởi gói mà nó nhắm mục tiêu hoặc bởi gói có quyền android.permission.CHANGE_OVERLAY_PACKAGES . Khi 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à khởi chạy lại các hoạt động mục tiêu.

Hạn chế tài nguyên có thể che 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 ví dụ sau tệp res/values/overlayable.xml , string/foointeger/bar là các tài nguyên được sử dụng để tạo chủ đề cho diện mạo của thiết bị; để che phủ các tài nguyên này, lớp phủ phải nhắm mục tiêu rõ ràng vào bộ sưu tập các tài nguyên có thể che 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>

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ụ: đó là:

  • OK để hai gói khác nhau xác định cả <overlayable name="foo"> .

  • Không ổn khi một APK có hai khối <overlayable name="foo"> .

Mã sau đây hiển thị 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> , lớp phủ sẽ nhắm mục tiêu ứng dụng đó:

  • Phải chỉ định targetName .

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

  • Chỉ có thể nhắm mục tiêu một tên <overlayable> .

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

Hạn chế chính sách

Sử dụng thẻ <policy> để thực thi các hạn chế đối với các tài nguyên có thể che phủ. Thuộc tính type chỉ định những chính sách nào mà lớp phủ phải đáp ứng để ghi đè các tài nguyên được bao gồm. Các loại được hỗ trợ bao gồm những điều sau đây.

  • 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 đè tài nguyên.
  • vendor . Bất kỳ lớp phủ nào trên phân vùng của nhà cung cấp đều có thể ghi đè 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 đè tài nguyên.
  • oem . Bất kỳ lớp phủ nào trên phân vùng OEM đều có thể ghi đè tài nguyên.
  • odm . Bất kỳ lớp phủ nào trên phân vùng odm đều có thể ghi đè tài nguyên.
  • signature . Bất kỳ lớp phủ nào được ký bằng cùng chữ ký với APK mục tiêu đều có thể ghi đè tài nguyên.
  • actor . Bất kỳ lớp phủ nào được ký bằng cùng chữ ký với APK diễn viên đều có thể ghi đè tài nguyên. Tác nhân được khai báo trong thẻ diễn viên có tên trong cấu hình hệ thống.
  • config_signature . Bất kỳ lớp phủ nào được ký bằng cùng chữ ký với apk cấu hình lớp phủ đều có thể ghi đè tài nguyên. Cấu hình lớp phủ được khai báo trong thẻ chữ ký lớp phủ-config trong cấu hình hệ thống.

Đoạn mã sau hiển thị 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 thanh dọc (|) làm ký tự phân cách. Khi nhiều chính sách được chỉ định, 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ợ các cơ chế khác nhau để đị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ủ tùy 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ì 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ủ.

  • Tất cả các thiết bị 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ể 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 ở 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, 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 hiển thị một ví dụ 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 tùy chọn kiểm soát xem lớp phủ có được bật theo mặc định hay không (mặc định là false ). Thuộc mutable có thể thay đổi tùy chọn kiểm soát xem lớp phủ có thể thay đổi được hay không và trạng thái kích hoạt của nó có thể thay đổi theo chương trình trong thời gian chạy (mặc định là true ). Các lớp phủ không được liệt kê trong tệp cấu hình có thể thay đổi và tắt theo mặc định.

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

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

Hợp nhất các tập tin

Sử dụng thẻ <merge> cho phép các tệp cấu hình khác được hợp nhất tại vị trí đã 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 liên quan đến 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á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 thay đổi, điều này ngăn lớp phủ bị vô hiệu hóa.

  • 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 nhiều lớp phủ tĩnh nhắm mục tiêu cùng một giá trị tài nguyên. Số cao hơn biểu thị mức độ ưu tiên cao hơn.

Đoạn mã sau đây hiển thị một ví dụ 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>

Những 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 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 quyền ư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 để tác động đến 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 kích hoạt thành phần, hãy sử dụng thẻ SystemConfig <component-override> (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 trình quản lý lớp phủ sau.

adb shell cmd overlay

OverlayManagerService sử dụng idmap2 để ánh xạ ID tài nguyên trong gói mục tiêu tới ID tài nguyên trong gói lớp phủ. Ánh xạ ID được tạo đượ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ủ 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 bản đồ tài nguyên như 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