Định dạng tệp APEX

Định dạng vùng chứa Android Pony EXpress (APEX) được giới thiệu trong Android 10 và được dùng trong quy trình cài đặt cho các mô-đun hệ thống cấp thấp hơn. Định dạng này hỗ trợ việc cập nhật các thành phần hệ thống không phù hợp với mô hình ứng dụng Android tiêu chuẩn. Một số thành phần mẫu là các dịch vụ và thư viện gốc, lớp trừu tượng phần cứng (HAL), thời gian chạy (ART) và thư viện lớp.

Thuật ngữ "APEX" cũng có thể đề cập đến tệp APEX.

Thông tin khái quát

Mặc dù Android hỗ trợ việc cập nhật các mô-đun phù hợp với mô hình ứng dụng tiêu chuẩn (ví dụ: dịch vụ, hoạt động) thông qua các ứng dụng trình cài đặt gói (chẳng hạn như ứng dụng Cửa hàng Google Play), nhưng việc sử dụng một mô hình tương tự cho các thành phần hệ điều hành cấp thấp hơn có các hạn chế sau:

  • Không thể sử dụng các mô-đun dựa trên APK ở giai đoạn đầu của trình tự khởi động. Trình quản lý gói là kho lưu trữ trung tâm của thông tin về ứng dụng và chỉ có thể được khởi động từ trình quản lý hoạt động, trình quản lý này sẽ sẵn sàng ở giai đoạn sau của quy trình khởi động.
  • Định dạng APK (đặc biệt là tệp kê khai) được thiết kế cho các ứng dụng Android và không phải lúc nào cũng phù hợp với các mô-đun hệ thống.

Thiết kế

Phần này mô tả thiết kế cấp cao của định dạng tệp APEX và trình quản lý APEX, đây là một dịch vụ quản lý các tệp APEX.

Để biết thêm thông tin về lý do chọn thiết kế này cho APEX, hãy xem phần Các phương án thay thế được xem xét khi phát triển APEX.

Định dạng APEX

Đây là định dạng của tệp APEX.

Định dạng tệp APEX

Hình 1. Định dạng tệp APEX

Ở cấp cao nhất, tệp APEX là tệp zip trong đó các tệp được lưu trữ không nén và nằm ở ranh giới 4 KB.

Có 4 tệp trong tệp APEX:

  • apex_manifest.json
  • AndroidManifest.xml
  • apex_payload.img
  • apex_pubkey

Tệp apex_manifest.json chứa tên và phiên bản gói, giúp xác định tệp APEX. Đây là vùng đệm giao thức ApexManifest ở định dạng JSON.

Tệp AndroidManifest.xml cho phép tệp APEX sử dụng các công cụ và cơ sở hạ tầng liên quan đến APK, chẳng hạn như ADB, PackageManager và các ứng dụng trình cài đặt gói (chẳng hạn như Cửa hàng Play). Ví dụ: tệp APEX có thể sử dụng một công cụ hiện có như aapt để kiểm tra siêu dữ liệu cơ bản từ tệp. Tệp này chứa tên gói và thông tin phiên bản. Thông tin này thường cũng có trong apex_manifest.json.

Bạn nên sử dụng apex_manifest.json thay vì AndroidManifest.xml cho mã mới và các hệ thống xử lý APEX. AndroidManifest.xml có thể chứa thêm thông tin nhắm mục tiêu mà các công cụ phát hành ứng dụng hiện có có thể sử dụng.

apex_payload.img là hình ảnh hệ thống tệp ext4 được dm-verity hỗ trợ. Hình ảnh được gắn trong thời gian chạy thông qua một thiết bị vòng lặp quy hồi. Cụ thể, cây băm và khối siêu dữ liệu được tạo bằng thư viện libavb. Tải trọng hệ thống tệp không được phân tích cú pháp (vì hình ảnh phải được gắn tại chỗ). Các tệp thông thường được đưa vào tệp apex_payload.img.

apex_pubkey là khoá công khai dùng để ký hình ảnh hệ thống tệp. Trong thời gian chạy, khoá này đảm bảo rằng APEX đã tải xuống được ký bằng cùng một thực thể ký cùng một APEX trong các phân vùng tích hợp.

Nguyên tắc đặt tên APEX

Để giúp ngăn chặn xung đột tên giữa các APEX mới khi nền tảng phát triển, hãy sử dụng các nguyên tắc đặt tên sau:

  • com.android.*
    • Dành riêng cho APEX AOSP. Không dành riêng cho bất kỳ công ty hoặc thiết bị nào.
  • com.<companyname>.*
    • Đã đặt trước cho một công ty. Có thể được nhiều thiết bị của công ty đó sử dụng.
  • com.<companyname>.<devicename>.*
    • Dành riêng cho các APEX dành riêng cho một thiết bị cụ thể (hoặc một nhóm nhỏ thiết bị).

Trình quản lý APEX

Trình quản lý APEX (hoặc apexd) là một quy trình gốc độc lập chịu trách nhiệm xác minh, cài đặt và gỡ cài đặt các tệp APEX. Quá trình này được khởi chạy và sẵn sàng ở giai đoạn đầu của trình tự khởi động. Các tệp APEX thường được cài đặt sẵn trên thiết bị trong /system/apex. Trình quản lý APEX mặc định sử dụng các gói này nếu không có bản cập nhật nào.

Trình tự cập nhật của APEX sử dụng lớp PackageManager như sau.

  1. Tệp APEX được tải xuống thông qua ứng dụng trình cài đặt gói, ADB hoặc nguồn khác.
  2. Trình quản lý gói bắt đầu quy trình cài đặt. Khi nhận ra rằng tệp là APEX, trình quản lý gói sẽ chuyển quyền kiểm soát sang trình quản lý APEX.
  3. Trình quản lý APEX xác minh tệp APEX.
  4. Nếu tệp APEX được xác minh, cơ sở dữ liệu nội bộ của trình quản lý APEX sẽ được cập nhật để phản ánh việc tệp APEX được kích hoạt trong lần khởi động tiếp theo.
  5. Trình yêu cầu cài đặt sẽ nhận được thông báo truyền tin sau khi xác minh gói thành công.
  6. Để tiếp tục cài đặt, bạn phải khởi động lại hệ thống.
  7. Ở lần khởi động tiếp theo, trình quản lý APEX sẽ khởi động, đọc cơ sở dữ liệu nội bộ và thực hiện các thao tác sau cho từng tệp APEX được liệt kê:

    1. Xác minh tệp APEX.
    2. Tạo thiết bị vòng lặp từ tệp APEX.
    3. Tạo một thiết bị khối trình ánh xạ thiết bị trên thiết bị vòng lặp.
    4. Gắn thiết bị khối trình liên kết thiết bị vào một đường dẫn duy nhất (ví dụ: /apex/name@ver).

Khi tất cả tệp APEX được liệt kê trong cơ sở dữ liệu nội bộ được gắn, trình quản lý APEX sẽ cung cấp dịch vụ liên kết cho các thành phần hệ thống khác để truy vấn thông tin về các tệp APEX đã cài đặt. Ví dụ: các thành phần hệ thống khác có thể truy vấn danh sách tệp APEX được cài đặt trong thiết bị hoặc truy vấn đường dẫn chính xác nơi một APEX cụ thể được gắn để có thể truy cập vào các tệp.

Tệp APEX là tệp APK

Tệp APEX là tệp APK hợp lệ vì đó là tệp lưu trữ zip đã ký (sử dụng lược đồ chữ ký APK) chứa tệp AndroidManifest.xml. Điều này cho phép các tệp APEX sử dụng cơ sở hạ tầng cho các tệp APK, chẳng hạn như ứng dụng trình cài đặt gói, tiện ích ký và trình quản lý gói.

Tệp AndroidManifest.xml bên trong tệp APEX có kích thước tối thiểu, bao gồm gói name, versionCodetargetSdkVersion, minSdkVersionmaxSdkVersion (không bắt buộc) để nhắm mục tiêu chi tiết. Thông tin này cho phép phân phối các tệp APEX thông qua các kênh hiện có, chẳng hạn như ứng dụng trình cài đặt gói và ADB.

Các loại tệp được hỗ trợ

Định dạng APEX hỗ trợ các loại tệp sau:

  • Thư viện dùng chung gốc
  • Tệp thực thi gốc
  • Tệp JAR
  • Tệp dữ liệu
  • Tệp cấu hình

Điều này không có nghĩa là APEX có thể cập nhật tất cả các loại tệp này. Việc có thể cập nhật loại tệp hay không phụ thuộc vào nền tảng và độ ổn định của định nghĩa giao diện cho các loại tệp.

Tuỳ chọn ký

Tệp APEX được ký theo hai cách. Trước tiên, tệp apex_payload.img (cụ thể là chỉ số mô tả vbmeta được thêm vào apex_payload.img) được ký bằng một khoá. Sau đó, toàn bộ APEX được ký bằng lược đồ chữ ký APK v3. Hai khoá khác nhau được sử dụng trong quy trình này.

Ở phía thiết bị, một khoá công khai tương ứng với khoá riêng tư dùng để ký mô tả vbmeta sẽ được cài đặt. Trình quản lý APEX sử dụng khoá công khai để xác minh các APEX được yêu cầu cài đặt. Mỗi APEX phải được ký bằng các khoá khác nhau và được thực thi cả tại thời điểm tạo bản dựng và tại thời gian chạy.

APEX trong các phân vùng tích hợp

Các tệp APEX có thể nằm trong các phân vùng tích hợp sẵn như /system. Phân vùng đã kết thúc dm-verity, vì vậy, các tệp APEX được gắn trực tiếp trên thiết bị vòng lặp.

Nếu có APEX trong một phân vùng tích hợp, bạn có thể cập nhật APEX bằng cách cung cấp một gói APEX có cùng tên gói và lớn hơn hoặc bằng mã phiên bản. APEX mới được lưu trữ trong /data và tương tự như tệp APK, phiên bản mới cài đặt sẽ che phiên bản đã có trong phân vùng tích hợp. Tuy nhiên, không giống như tệp APK, phiên bản APEX mới cài đặt chỉ được kích hoạt sau khi khởi động lại.

Yêu cầu về nhân

Để hỗ trợ các mô-đun chính của APEX trên thiết bị Android, bạn cần có các tính năng hạt nhân Linux sau: trình điều khiển vòng lặp và dm-verity. Trình điều khiển vòng lặp sẽ gắn hình ảnh hệ thống tệp trong mô-đun APEX và dm-verity sẽ xác minh mô-đun APEX.

Hiệu suất của trình điều khiển loopback và dm-verity đóng vai trò quan trọng trong việc đạt được hiệu suất hệ thống tốt khi sử dụng các mô-đun APEX.

Các phiên bản kernel được hỗ trợ

Các mô-đun chính của APEX được hỗ trợ trên các thiết bị sử dụng phiên bản nhân 4.4 trở lên. Các thiết bị mới chạy Android 10 trở lên phải sử dụng hạt nhân phiên bản 4.9 trở lên để hỗ trợ các mô-đun APEX.

Bản vá hạt nhân bắt buộc

Các bản vá hạt nhân bắt buộc để hỗ trợ các mô-đun APEX được đưa vào cây phổ biến của Android. Để tải các bản vá hỗ trợ APEX, hãy sử dụng phiên bản mới nhất của cây chung Android.

Kernel phiên bản 4.4

Phiên bản này chỉ được hỗ trợ cho các thiết bị được nâng cấp từ Android 9 lên Android 10 và muốn hỗ trợ các mô-đun APEX. Để nhận các bản vá bắt buộc, bạn nên hợp nhất xuống từ nhánh android-4.4. Dưới đây là danh sách các bản vá riêng lẻ bắt buộc cho nhân phiên bản 4.4.

  • UPSTREAM: loop: add ioctl for changing logical block size (4.4)
  • BACKPORT: block/loop: set hw_sectors (4.4)
  • UPSTREAM: loop: Thêm LOOP_SET_BLOCK_SIZE trong ioctl tương thích (4.4)
  • ANDROID: mnt: Sửa next_descendent (4.4)
  • ANDROID: mnt: remount should propagate to slaves of slaves (4.4)
  • ANDROID: mnt: Truyền tải đúng cách việc gắn lại (4.4)
  • Huỷ bỏ "ANDROID: dm verity: add minimum prefetch size" (ANDROID: dm verity: thêm kích thước tải trước tối thiểu) (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

Phiên bản kernel 4.9/4.14/4.19

Để tải các bản vá bắt buộc cho phiên bản hạt nhân 4.9/4.14/4.19, hãy hợp nhất xuống từ nhánh android-common.

Các tuỳ chọn cấu hình hạt nhân bắt buộc

Danh sách sau đây cho thấy các yêu cầu về cấu hình cơ sở để hỗ trợ các mô-đun APEX được giới thiệu trong Android 10. Các mục có dấu hoa thị (*) là các yêu cầu hiện tại từ Android 9 trở xuống.

(*) CONFIG_AIO=Y # AIO support (for direct I/O on loop devices)
CONFIG_BLK_DEV_LOOP=Y # for loop device support
CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 # pre-create 16 loop devices
(*) CONFIG_CRYPTO_SHA1=Y # SHA1 hash for DM-verity
(*) CONFIG_CRYPTO_SHA256=Y # SHA256 hash for DM-verity
CONFIG_DM_VERITY=Y # DM-verity support

Yêu cầu về tham số dòng lệnh của hạt nhân

Để hỗ trợ APEX, hãy đảm bảo các thông số dòng lệnh của hạt nhân đáp ứng các yêu cầu sau:

  • KHÔNG được đặt loop.max_loop
  • loop.max_part phải <= 8

Tạo APEX

Phần này mô tả cách tạo APEX bằng hệ thống xây dựng Android. Sau đây là ví dụ về Android.bp cho một APEX có tên là apex.test.

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    // libc.so and libcutils.so are included in the apex
    native_shared_libs: ["libc", "libcutils"],
    binaries: ["vold"],
    java_libs: ["core-all"],
    prebuilts: ["my_prebuilt"],
    compile_multilib: "both",
    key: "apex.test.key",
    certificate: "platform",
}

Ví dụ về apex_manifest.json:

{
  "name": "com.android.example.apex",
  "version": 1
}

Ví dụ về file_contexts:

(/.*)?           u:object_r:system_file:s0
/sub(/.*)?       u:object_r:sub_file:s0
/sub/file3       u:object_r:file3_file:s0

Loại và vị trí tệp trong APEX

Loại tệp Vị trí trong APEX
Thư viện chia sẻ /lib/lib64 (/lib/arm cho arm đã dịch trong x86)
Tệp thực thi /bin
Thư viện Java /javalib
Tệp tạo sẵn /etc

Phần phụ thuộc bắc cầu

Tệp APEX tự động bao gồm các phần phụ thuộc bắc cầu của thư viện dùng chung gốc hoặc tệp thực thi. Ví dụ: nếu libFoo phụ thuộc vào libBar, thì hai thư viện này sẽ được đưa vào khi chỉ libFoo được liệt kê trong thuộc tính native_shared_libs.

Xử lý nhiều ABI

Cài đặt thuộc tính native_shared_libs cho cả giao diện nhị phân của ứng dụng (ABI) chính và phụ của thiết bị. Nếu một APEX nhắm đến các thiết bị có một ABI (tức là chỉ 32 bit hoặc chỉ 64 bit), thì chỉ các thư viện có ABI tương ứng mới được cài đặt.

Chỉ cài đặt thuộc tính binaries cho ABI chính của thiết bị như mô tả bên dưới:

  • Nếu thiết bị chỉ hỗ trợ 32 bit, thì chỉ biến thể 32 bit của tệp nhị phân mới được cài đặt.
  • Nếu thiết bị chỉ hỗ trợ 64 bit, thì chỉ biến thể 64 bit của tệp nhị phân mới được cài đặt.

Để thêm quyền kiểm soát chi tiết đối với ABI của các thư viện gốc và tệp nhị phân, hãy sử dụng các thuộc tính multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].

  • first: Khớp với ABI chính của thiết bị. Đây là giá trị mặc định cho tệp nhị phân.
  • lib32: Khớp với ABI 32 bit của thiết bị, nếu được hỗ trợ.
  • lib64: Khớp với ABI 64 bit của thiết bị, thiết bị đó được hỗ trợ.
  • prefer32: Khớp với ABI 32 bit của thiết bị, nếu được hỗ trợ. Nếu không hỗ trợ ABI 32 bit, hãy so khớp với ABI 64 bit.
  • both: So khớp với cả hai ABI. Đây là giá trị mặc định cho native_shared_libraries.

Các thuộc tính java, librariesprebuilts không phụ thuộc vào ABI.

Ví dụ này dành cho một thiết bị hỗ trợ 32/64 và không ưu tiên 32:

apex {
    // other properties are omitted
    native_shared_libs: ["libFoo"], // installed for 32 and 64
    binaries: ["exec1"], // installed for 64, but not for 32
    multilib: {
        first: {
            native_shared_libs: ["libBar"], // installed for 64, but not for 32
            binaries: ["exec2"], // same as binaries without multilib.first
        },
        both: {
            native_shared_libs: ["libBaz"], // same as native_shared_libs without multilib
            binaries: ["exec3"], // installed for 32 and 64
        },
        prefer32: {
            native_shared_libs: ["libX"], // installed for 32, but not for 64
        },
        lib64: {
            native_shared_libs: ["libY"], // installed for 64, but not for 32
        },
    },
}

Ký vbmeta

Ký từng APEX bằng các khoá khác nhau. Khi cần một khoá mới, hãy tạo một cặp khoá công khai-riêng tư và tạo một mô-đun apex_key. Sử dụng thuộc tính key để ký APEX bằng khoá. Khoá công khai được tự động đưa vào APEX với tên avb_pubkey.

# create an rsa key pair
openssl genrsa -out foo.pem 4096

# extract the public key from the key pair
avbtool extract_public_key --key foo.pem --output foo.avbpubkey

# in Android.bp
apex_key {
    name: "apex.test.key",
    public_key: "foo.avbpubkey",
    private_key: "foo.pem",
}

Trong ví dụ trên, tên của khoá công khai (foo) trở thành mã nhận dạng của khoá. Mã nhận dạng của khoá dùng để ký APEX được ghi trong APEX. Trong thời gian chạy, apexd xác minh APEX bằng cách sử dụng khoá công khai có cùng mã nhận dạng trong thiết bị.

Ký APEX

Ký APEX theo cách tương tự như cách bạn ký APK. Ký APEX hai lần; một lần cho hệ thống tệp mini (tệp apex_payload.img) và một lần cho toàn bộ tệp.

Để ký APEX ở cấp tệp, hãy đặt thuộc tính certificate theo một trong ba cách sau:

  • Chưa được đặt: Nếu bạn không đặt giá trị nào, APEX sẽ được ký bằng chứng chỉ nằm tại PRODUCT_DEFAULT_DEV_CERTIFICATE. Nếu bạn không đặt cờ nào, đường dẫn mặc định sẽ là build/target/product/security/testkey.
  • <name>: APEX được ký bằng chứng chỉ <name> trong cùng thư mục với PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: APEX được ký bằng chứng chỉ do mô-đun Soong tên là <name> xác định. Bạn có thể xác định mô-đun chứng chỉ như sau.
android_app_certificate {
    name: "my_key_name",
    certificate: "dir/cert",
    // this will use dir/cert.x509.pem (the cert) and dir/cert.pk8 (the private key)
}

Cài đặt APEX

Để cài đặt APEX, hãy sử dụng ADB.

adb install apex_file_name
adb reboot

Nếu supportsRebootlessUpdate được đặt thành true trong apex_manifest.json và APEX hiện đã cài đặt không được sử dụng (ví dụ: mọi dịch vụ trong đó đã bị dừng), thì bạn có thể cài đặt APEX mới mà không cần khởi động lại bằng cờ --force-non-staged.

adb install --force-non-staged apex_file_name

Sử dụng APEX

Sau khi khởi động lại, APEX sẽ được gắn vào thư mục /apex/<apex_name>@<version>. Bạn có thể gắn nhiều phiên bản của cùng một APEX cùng lúc. Trong số các đường dẫn gắn, đường dẫn tương ứng với phiên bản mới nhất được liên kết gắn tại /apex/<apex_name>.

Ứng dụng khách có thể sử dụng đường dẫn liên kết được gắn để đọc hoặc thực thi tệp từ APEX.

APEX thường được sử dụng như sau:

  1. OEM hoặc ODM tải trước APEX trong /system/apex khi thiết bị được vận chuyển.
  2. Các tệp trong APEX được truy cập thông qua đường dẫn /apex/<apex_name>/.
  3. Khi phiên bản APEX đã cập nhật được cài đặt trong /data/apex, đường dẫn sẽ trỏ đến APEX mới sau khi khởi động lại.

Cập nhật dịch vụ bằng APEX

Cách cập nhật dịch vụ bằng APEX:

  1. Đánh dấu dịch vụ trong phân vùng hệ thống là có thể cập nhật. Thêm tuỳ chọn updatable vào phần khai báo dịch vụ.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. Tạo tệp .rc mới cho dịch vụ đã cập nhật. Sử dụng tuỳ chọn override để xác định lại dịch vụ hiện có.

    /apex/my.apex/etc/init.rc:
    
    service myservice /apex/my.apex/bin/myservice
        class core
        user system
        ...
        override
    

Bạn chỉ có thể xác định định nghĩa dịch vụ trong tệp .rc của APEX. Trình kích hoạt hành động không được hỗ trợ trong APEX.

Nếu một dịch vụ được đánh dấu là có thể cập nhật bắt đầu trước khi các APEX được kích hoạt, thì quá trình bắt đầu sẽ bị trì hoãn cho đến khi quá trình kích hoạt các APEX hoàn tất.

Định cấu hình hệ thống để hỗ trợ các bản cập nhật APEX

Đặt thuộc tính hệ thống sau thành true để hỗ trợ các bản cập nhật tệp APEX.

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

hoặc chỉ

<device.mk>:

$(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)

APEX được làm phẳng

Đối với các thiết bị cũ, đôi khi không thể hoặc không khả thi để cập nhật hạt nhân cũ nhằm hỗ trợ đầy đủ APEX. Ví dụ: hạt nhân có thể đã được tạo mà không có CONFIG_BLK_DEV_LOOP=Y, điều này rất quan trọng để gắn hình ảnh hệ thống tệp bên trong APEX.

APEX được làm phẳng là một APEX được tạo đặc biệt có thể được kích hoạt trên các thiết bị có hạt nhân cũ. Các tệp trong APEX được làm phẳng được cài đặt trực tiếp vào một thư mục trong phân vùng tích hợp. Ví dụ: lib/libFoo.so trong my.apex APEX được làm phẳng được cài đặt vào /system/apex/my.apex/lib/libFoo.so.

Việc kích hoạt APEX được làm phẳng không liên quan đến thiết bị lặp. Toàn bộ thư mục /system/apex/my.apex được liên kết trực tiếp với /apex/name@ver.

Bạn không thể cập nhật các APEX đã làm phẳng bằng cách tải các phiên bản APEX đã cập nhật xuống từ mạng vì không thể làm phẳng các APEX đã tải xuống. Bạn chỉ có thể cập nhật các APEX được làm phẳng thông qua OTA thông thường.

APEX được làm phẳng là cấu hình mặc định. Điều này có nghĩa là tất cả APEX đều được làm phẳng theo mặc định, trừ phi bạn định cấu hình rõ ràng thiết bị để tạo APEX không bị làm phẳng nhằm hỗ trợ bản cập nhật APEX (như giải thích ở trên).

KHÔNG hỗ trợ việc kết hợp các APEX được làm phẳng và không được làm phẳng trong một thiết bị. Tất cả APEX trong một thiết bị phải không được làm phẳng hoặc phải được làm phẳng. Điều này đặc biệt quan trọng khi vận chuyển các bản dựng APEX được ký trước cho các dự án như Mainline. Các APEX không được ký trước (tức là được tạo từ nguồn) cũng không được làm phẳng và phải được ký bằng các khoá thích hợp. Thiết bị sẽ kế thừa từ updatable_apex.mk như được giải thích trong phần Cập nhật dịch vụ bằng APEX.

APEX nén

Android 12 trở lên có tính năng nén APEX để giảm tác động của bộ nhớ đối với các gói APEX có thể cập nhật. Sau khi cài đặt bản cập nhật cho APEX, mặc dù phiên bản cài đặt sẵn không còn được sử dụng nữa, nhưng phiên bản này vẫn chiếm cùng một dung lượng. Không gian đã chiếm vẫn không dùng được.

Tính năng nén APEX giảm thiểu tác động đến bộ nhớ này bằng cách sử dụng một tập hợp các tệp APEX được nén chặt chẽ trên các phân vùng chỉ có thể đọc (chẳng hạn như phân vùng /system). Android 12 trở lên sử dụng thuật toán nén zip DEFLATE.

Tính năng nén không tối ưu hoá những tệp sau:

  • Các APEX khởi động cần được gắn rất sớm trong trình tự khởi động.

  • APEX không thể cập nhật. Tính năng nén chỉ có lợi nếu bạn cài đặt phiên bản APEX đã cập nhật trên phân vùng /data. Bạn có thể xem danh sách đầy đủ các APEX có thể cập nhật trên trang Thành phần hệ thống mô-đun.

  • APEXes thư viện dùng chung động. Vì apexd luôn kích hoạt cả hai phiên bản APEX (đã cài đặt sẵn và đã nâng cấp), nên việc nén các phiên bản này sẽ không làm tăng giá trị.

Định dạng tệp APEX nén

Đây là định dạng của tệp APEX nén.

Sơ đồ cho thấy định dạng của tệp APEX nén

Hình 2. Định dạng tệp APEX nén

Ở cấp cao nhất, tệp APEX nén là tệp zip chứa tệp apex gốc ở dạng rút gọn với mức độ nén là 9 và các tệp khác được lưu trữ không nén.

Một tệp APEX bao gồm 4 tệp:

  • original_apex: được giảm áp với mức độ nén là 9 Đây là tệp APEX gốc, chưa nén.
  • apex_manifest.pb: chỉ lưu trữ
  • AndroidManifest.xml: chỉ lưu trữ
  • apex_pubkey: chỉ lưu trữ

Các tệp apex_manifest.pb, AndroidManifest.xmlapex_pubkey là bản sao của các tệp tương ứng trong original_apex.

Tạo APEX nén

Bạn có thể tạo APEX nén bằng công cụ apex_compression_tool.py tại system/apex/tools.

Có một số tham số liên quan đến tính năng nén APEX trong hệ thống xây dựng.

Trong Android.bp, thuộc tính compressible sẽ kiểm soát việc tệp APEX có thể nén hay không:

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    compressible: true,
}

Cờ sản phẩm PRODUCT_COMPRESSED_APEX kiểm soát việc hình ảnh hệ thống được tạo từ nguồn có phải chứa các tệp APEX nén hay không.

Đối với thử nghiệm cục bộ, bạn có thể buộc một bản dựng nén các APEX bằng cách đặt OVERRIDE_PRODUCT_COMPRESSED_APEX= thành true.

Các tệp APEX nén do hệ thống xây dựng tạo có đuôi .capex. Tiện ích này giúp bạn dễ dàng phân biệt giữa phiên bản nén và không nén của tệp APEX.

Các thuật toán nén được hỗ trợ

Android 12 chỉ hỗ trợ tính năng nén deflate-zip.

Kích hoạt tệp APEX nén trong quá trình khởi động

Trước khi có thể kích hoạt APEX nén, tệp original_apex bên trong tệp đó sẽ được giải nén vào thư mục /data/apex/decompressed. Tệp APEX đã giải nén thu được được liên kết cứng với thư mục /data/apex/active.

Hãy xem ví dụ sau đây để minh hoạ quy trình được mô tả ở trên.

Hãy xem /system/apex/com.android.foo.capex là một APEX nén đang được kích hoạt, với versionCode 37.

  1. Tệp original_apex bên trong /system/apex/com.android.foo.capex được giải nén thành /data/apex/decompressed/com.android.foo@37.apex.
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex được thực hiện để xác minh rằng ứng dụng có nhãn SELinux chính xác.
  3. Các bước kiểm tra xác minh được thực hiện trên /data/apex/decompressed/com.android.foo@37.apex để đảm bảo tính hợp lệ của khoá này: apexd kiểm tra khoá công khai đi kèm trong /data/apex/decompressed/com.android.foo@37.apex để xác minh rằng khoá này bằng khoá đi kèm trong /system/apex/com.android.foo.capex.
  4. Tệp /data/apex/decompressed/com.android.foo@37.apex được liên kết cứng với thư mục /data/apex/active/com.android.foo@37.apex.
  5. Logic kích hoạt thông thường cho các tệp APEX chưa nén được thực hiện trên /data/apex/active/com.android.foo@37.apex.

Tương tác với OTA

Các tệp APEX nén có ảnh hưởng đến việc phân phối và ứng dụng OTA. Vì bản cập nhật OTA có thể chứa tệp APEX nén có cấp phiên bản cao hơn cấp đang hoạt động trên thiết bị, nên bạn phải dành một lượng dung lượng trống nhất định trước khi khởi động lại thiết bị để áp dụng bản cập nhật OTA.

Để hỗ trợ hệ thống OTA, apexd hiển thị hai API liên kết sau:

  • calculateSizeForCompressedApex – tính toán kích thước cần thiết để giải nén các tệp APEX trong gói OTA. Bạn có thể dùng URL này để xác minh rằng thiết bị có đủ dung lượng trước khi tải bản cập nhật OTA xuống.
  • reserveSpaceForCompressedApex – dành trước dung lượng trên ổ đĩa để apexd sử dụng trong tương lai nhằm giải nén các tệp APEX nén bên trong gói OTA.

Trong trường hợp cập nhật OTA A/B, apexd sẽ thử giải nén trong nền trong quy trình OTA sau khi cài đặt. Nếu không giải nén được, apexd sẽ giải nén trong quá trình khởi động để áp dụng bản cập nhật OTA.

Các giải pháp thay thế được xem xét khi phát triển APEX

Sau đây là một số tuỳ chọn mà AOSP đã cân nhắc khi thiết kế định dạng tệp APEX và lý do các tuỳ chọn này được đưa vào hoặc bị loại trừ.

Hệ thống quản lý gói thông thường

Các bản phân phối Linux có các hệ thống quản lý gói như dpkgrpm, mạnh mẽ, trưởng thành và mạnh mẽ. Tuy nhiên, các tệp này không được sử dụng cho APEX vì không thể bảo vệ các gói sau khi cài đặt. Quy trình xác minh chỉ được thực hiện khi các gói đang được cài đặt. Những kẻ tấn công có thể phá vỡ tính toàn vẹn của các gói đã cài đặt mà không bị phát hiện. Đây là một hồi quy cho Android, trong đó tất cả các thành phần hệ thống được lưu trữ trong hệ thống tệp chỉ có thể đọc, có tính toàn vẹn được bảo vệ bằng dm-verity cho mọi I/O. Mọi hành vi can thiệp vào các thành phần hệ thống đều phải bị cấm hoặc phát hiện được để thiết bị có thể từ chối khởi động nếu bị xâm phạm.

dm-crypt để đảm bảo tính toàn vẹn

Các tệp trong vùng chứa APEX là từ các phân vùng tích hợp (ví dụ: phân vùng /system) được bảo vệ bằng dm-verity, trong đó mọi nội dung sửa đổi đối với các tệp đều bị cấm ngay cả sau khi các phân vùng được gắn. Để cung cấp cùng một cấp độ bảo mật cho các tệp, tất cả tệp trong APEX đều được lưu trữ trong một hình ảnh hệ thống tệp được ghép nối với cây băm và chỉ số mô tả vbmeta. Nếu không có dm-verity, APEX trong phân vùng /data sẽ dễ bị sửa đổi ngoài ý muốn sau khi được xác minh và cài đặt.

Trên thực tế, phân vùng /data cũng được bảo vệ bằng các lớp mã hoá như dm-crypt. Mặc dù phương thức này cung cấp một mức độ bảo vệ nhất định chống lại hành vi can thiệp, nhưng mục đích chính của phương thức này là quyền riêng tư chứ không phải tính toàn vẹn. Khi kẻ tấn công có quyền truy cập vào phân vùng /data, không thể có biện pháp bảo vệ nào khác. Đây lại là một sự hồi quy so với mọi thành phần hệ thống nằm trong phân vùng /system. Cây băm bên trong tệp APEX cùng với dm-verity cung cấp cùng một mức độ bảo vệ nội dung.

Chuyển hướng đường dẫn từ /system đến /apex

Bạn có thể truy cập vào các tệp thành phần hệ thống được đóng gói trong APEX thông qua các đường dẫn mới như /apex/<name>/lib/libfoo.so. Khi các tệp thuộc phân vùng /system, bạn có thể truy cập vào các tệp đó thông qua các đường dẫn như /system/lib/libfoo.so. Ứng dụng của tệp APEX (các tệp APEX khác hoặc nền tảng) phải sử dụng đường dẫn mới. Bạn có thể cần cập nhật mã hiện có do thay đổi đường dẫn.

Mặc dù một cách để tránh thay đổi đường dẫn là phủ nội dung tệp trong tệp APEX lên phân vùng /system, nhưng nhóm Android đã quyết định không phủ các tệp trên phân vùng /system vì điều này có thể ảnh hưởng đến hiệu suất khi số lượng tệp được phủ (thậm chí có thể xếp chồng lên nhau) tăng lên.

Một lựa chọn khác là xâm nhập vào các hàm truy cập tệp như open, statreadlink để các đường dẫn bắt đầu bằng /system được chuyển hướng đến các đường dẫn tương ứng trong /apex. Nhóm Android đã loại bỏ tuỳ chọn này vì không thể thay đổi tất cả các hàm chấp nhận đường dẫn. Ví dụ: một số ứng dụng liên kết tĩnh với Bionic để triển khai các hàm. Trong những trường hợp như vậy, các ứng dụng đó sẽ không được chuyển hướng.