Đị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. Đị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 ví dụ là các dịch vụ và thư viện gốc, cá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 một tệp APEX.

Thông tin khái quát

Mặc dù Android hỗ trợ các bản cập nhật cho những 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 có những nhược điểm sau:

  • Bạn 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ữ thông tin trung tâm về các ứng dụng và chỉ có thể 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à các mô-đun hệ thống không phải lúc nào cũng phù hợp.

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 (một dịch vụ quản lý các tệp APEX).

Để biết thêm thông tin về lý do lựa chọn thiết kế này cho APEX, hãy xem phần Các lựa chọn thay thế được cân nhắc 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à một tệp zip trong đó các tệp được lưu trữ mà không nén và nằm ở ranh giới 4 KB.

Bốn tệp trong tệp APEX là:

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

Tệp apex_manifest.json chứa tên gói và phiên bản, giúp xác định một tệp APEX. Đây là một 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 trong 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 dùng apex_manifest.json thay vì AndroidManifest.xml cho mã và hệ thống mới xử lý APEX. AndroidManifest.xml có thể chứa thông tin nhắm mục tiêu bổ sung mà các công cụ xuất bản ứng dụng hiện có có thể sử dụng.

apex_payload.img là một hình ảnh hệ thống tệp ext4 được hỗ trợ bởi dm-verity. Hình ảnh được gắn tại 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ý APEX đó trong các phân vùng tích hợp.

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

Để tránh xung đột về tên giữa các APEX mới khi nền tảng phát triển, hãy tuân theo 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>.*
    • Được giữ riêng 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 duy nhất của 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 ngay từ đầu trong 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.

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

  1. Tệp APEX được tải xuống thông qua một ứ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 sẽ bắt đầu quy trình cài đặt. Khi nhận ra rằng tệp là một APEX, trình quản lý gói sẽ chuyển quyền kiểm soát cho 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 rằng tệp APEX được kích hoạt khi khởi động tiếp theo.
  5. Người yêu cầu cài đặt sẽ nhận được một thông báo 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. Vào 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 những thao tác sau cho từng tệp APEX được liệt kê:

    1. Xác minh tệp APEX.
    2. Tạo một 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 của trình ánh xạ thiết bị vào một đường dẫn duy nhất (ví dụ: /apex/name@ver).

Khi tất cả các tệp APEX có trong cơ sở dữ liệu nội bộ được gắn kết, trình quản lý APEX sẽ cung cấp một dịch vụ liên kết để 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, nhờ đó 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ì đây là các kho lưu trữ zip đã ký (bằ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 là tệp tối thiểu, bao gồm gói name, versionCodetargetSdkVersion, minSdkVersion (không bắt buộc) và maxSdkVersion để nhắm đến mục tiêu một cách chi tiết. Thông tin này cho phép các tệp APEX được phân phối 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 một loại tệp hay không phụ thuộc vào nền tảng và mức độ ổn định của các định nghĩa về giao diện cho các loại tệp.

Lựa chọn ký

Các tệp APEX được ký theo hai cách. Trước tiên, tệp apex_payload.img (cụ thể là vbmeta descriptor được thêm vào apex_payload.img) được ký bằng một khoá. Sau đó, toàn bộ APEX sẽ được ký bằng lược đồ chữ ký APK v3. Hai khoá riêng biệt được dùng trong quy trình này.

Về phía thiết bị, một khoá công khai tương ứng với khoá riêng tư dùng để ký bộ mô tả vbmeta sẽ được cài đặt. Trình quản lý APEX 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 gian xây dựng và thời gian chạy.

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

Bạn có thể tìm thấy các tệp APEX trong các phân vùng tích hợp như /system. Phân vùng đã được xác minh bằng dm-verity, vì vậy, các tệp APEX được kết nối trực tiếp qua 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à mã phiên bản lớn hơn hoặc bằng. APEX mới được lưu trữ trong /data và tương tự như APK, phiên bản mới cài đặt sẽ thay thế phiên bản đã có trong phân vùng tích hợp. Tuy nhiên, không giống như 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ề kernel

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

Hiệu suất của trình điều khiển vòng lặp và dm-verity là yếu tố quan trọng để đạ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 APEX được hỗ trợ trên các thiết bị sử dụng nhân phiên bả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 phiên bản nhân 4.9 trở lên để hỗ trợ các mô-đun APEX.

Các bản vá bắt buộc cho nhân

Các bản vá hạt nhân bắt buộc để hỗ trợ các mô-đun APEX có trong cây chung của Android. Để nhận 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 những 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 từ nhánh android-4.4. Sau đây là danh sách các bản vá riêng lẻ bắt buộc cho phiên bản kernel 4.4.

  • UPSTREAM: loop: add ioctl for changing logical block size (4.4)
  • BACKPORT: block/loop: set hw_sectors<0x0A>(4.4)
  • UPSTREAM: loop: Add LOOP_SET_BLOCK_SIZE in compat ioctl (4.4)
  • ANDROID: mnt: Fix next_descendent (4.4)
  • ANDROID: mnt: remount should propagate to slaves of slaves (4.4)
  • ANDROID: mnt: Propagate remount correctly (4.4)
  • Quay lại "ANDROID: dm verity: add minimum prefetch size" (ANDROID: dm verity: thêm kích thước tìm nạp tối thiểu) (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

Các phiên bản kernel 4.9/4.14/4.19

Để nhận các bản vá cần thiết cho phiên bản kernel 4.9/4.14/4.19, hãy hợp nhất từ nhánh android-common.

Các lựa chọn cấu hình kernel bắt buộc

Danh sách sau đây cho biết các yêu cầu về cấu hình cơ bản để 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à những yêu cầu hiện tại đối với 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 nhân

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

  • Bạn 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 tạo của 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 tệp và vị trí trong APEX

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

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

Các tệp APEX tự động bao gồm các phần phụ thuộc bắc cầu của các thư viện dùng chung hoặc tệp thực thi gốc. Ví dụ: nếu libFoo phụ thuộc vào libBar, thì hai thư viện 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 ứ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 duy nhất (tức là chỉ 32 bit hoặc chỉ 64 bit), thì chỉ những 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ỉ có biến thể 32 bit của tệp nhị phân được cài đặt.
  • Nếu thiết bị chỉ hỗ trợ 64 bit, thì chỉ có biến thể 64 bit của tệp nhị phân đượ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 và tệp nhị phân gốc, hãy sử dụng các thuộc tính multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].

  • first: Phù hợp với ABI chính của thiết bị. Đây là giá trị mặc định cho các tệp nhị phân.
  • lib32: Phù hợp với ABI 32 bit của thiết bị (nếu được hỗ trợ).
  • lib64: Phù hợp với ABI 64 bit của thiết bị, được hỗ trợ.
  • prefer32: Phù hợp với ABI 32 bit của thiết bị (nếu được hỗ trợ). Nếu ABI 32 bit không được hỗ trợ, hãy so khớp với ABI 64 bit.
  • both: So khớp 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 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 có 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 sẽ tự động được đưa vào APEX có 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) sẽ trở thành mã nhận dạng của khoá. Mã nhận dạng của khoá dùng để ký một APEX được ghi trong APEX. Trong thời gian chạy, apexd sẽ xác minh APEX bằ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ý một APEX ở cấp tệp, hãy đặt thuộc tính certificate theo một trong 3 cách sau:

  • Chưa đặ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 sẽ mặc định là build/target/product/security/testkey.
  • <name>: APEX được ký bằng chứng chỉ <name> trong cùng một thư mục với PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: APEX được ký bằng chứng chỉ do mô-đun Soong có tên <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 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 dùng (ví dụ: mọi dịch vụ mà APEX đó chứa đều đã dừng), thì bạn có thể cài đặt mộ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 kết tại 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 kết, đường dẫn tương ứng với phiên bản mới nhất được gắn kết tại /apex/<apex_name>.

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

APEX thường được dùng như sau:

  1. OEM hoặc ODM tải trước một 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 một phiên bản APEX mới đượ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 mộ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 lựa chọn updatable vào định nghĩa dịch vụ.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. Tạo một tệp .rc mới cho dịch vụ đã cập nhật. Sử dụng lựa 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 các định nghĩa dịch vụ trong tệp .rc của APEX. Các 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 khởi động 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 phân tách

Đối với các thiết bị cũ, đôi khi không thể hoặc không thực tế khi cập nhật nhân cũ để hỗ trợ đầy đủ APEX. Ví dụ: có thể nhân đã đượ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 rút gọn 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ó nhân cũ. Các tệp trong APEX được rút gọn sẽ đượ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 APEX được làm phẳng my.apex được cài đặt vào /system/apex/my.apex/lib/libFoo.so.

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

Bạn không thể cập nhật các APEX được 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 APEX được rút gọn thông qua một bản cập nhật OTA thông thường.

APEX được đơn giản hoá là cấu hình mặc định. Điều này có nghĩa là theo mặc định, tất cả APEX đều được làm phẳng, trừ phi bạn định cấu hình rõ ràng thiết bị của mình để tạo APEX không được làm phẳng nhằm hỗ trợ các 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à chưa được làm phẳng trong một thiết bị. Các APEX trong một thiết bị phải là tất cả các APEX chưa được làm phẳng hoặc tất cả các APEX đã được làm phẳng. Điều này đặc biệt quan trọng khi vận chuyển các APEX được tạo sẵn đã 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 phải không được làm phẳng và được ký bằng các khoá thích hợp. Thiết bị phải kế thừa từ updatable_apex.mk như 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 các gói APEX có thể cập nhật đến bộ nhớ. Sau khi một bản cập nhật cho APEX được cài đặt, mặc dù phiên bản cài đặt sẵn của APEX không còn được dùng nữa, nhưng phiên bản này vẫn chiếm cùng một lượng dung lượng. Không gian đã sử dụng đó 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 bộ tệp APEX được nén cao trên các phân vùng chỉ đọ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á cho những nội dung sau:

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

  • Các APEX không thể cập nhật. Việc nén chỉ có lợi nếu một phiên bản APEX mới được cài đặ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 Các thành phần hệ thống theo mô-đun.

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

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

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

Sơ đồ minh hoạ định dạng của một 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à một tệp zip chứa tệp apex gốc ở dạng giảm phát với mức nén là 9 và các tệp khác được lưu trữ ở dạng không nén.

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

  • original_apex: giảm với mức nén là 9. Đây là tệp APEX gốc, chưa nén.
  • apex_manifest.pb: chỉ được lưu trữ
  • AndroidManifest.xml: chỉ được lưu trữ
  • apex_pubkey: chỉ được 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 nằm tại system/apex/tools.

Hệ thống xây dựng có một số tham số liên quan đến việc nén APEX.

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

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ó 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 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 ra 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à phiên bản 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ợ chế độ nén deflate-zip.

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

Trước khi một APEX nén có thể được kích hoạt, tệp original_apex bên trong 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 xét /system/apex/com.android.foo.capex là một APEX nén đang được kích hoạt, có versionCode là 37.

  1. Tệp original_apex bên trong /system/apex/com.android.foo.capex sẽ đượ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 nó 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 được đi kèm trong /data/apex/decompressed/com.android.foo@37.apex để xác minh rằng khoá này bằng với khoá được đ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à áp dụng OTA. Vì bản cập nhật qua mạng (OTA) có thể chứa một tệp APEX nén có cấp phiên bản cao hơn cấp phiên bản đ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 sẽ hiển thị 2 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 một gói OTA. Bạn có thể dùng API này để xác minh rằng thiết bị có đủ dung lượng trước khi tải một bản cập nhật OTA xuống.
  • reserveSpaceForCompressedApex – dành riêng 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 qua mạng không dây A/B, apexd sẽ cố gắng giải nén ở chế độ nền trong quá trình cập nhật qua mạng không dây sau khi cài đặt. Nếu giải nén không thành công, apexd sẽ thực hiện quá trình giải nén trong quá trình khởi động áp dụng bản cập nhật OTA.

Các lựa chọn thay thế được cân nhắc khi phát triển APEX

Sau đây là một số lựa chọn mà AOSP đã cân nhắc khi thiết kế định dạng tệp APEX và lý do chúng được đưa vào hoặc 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, đây là những hệ thống mạnh mẽ, hoàn thiện và hiệu quả. Tuy nhiên, các tệp này không được áp 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. 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 bước thụt lùi đối với Android, nơi tất cả các thành phần hệ thống đều được lưu trữ trong các hệ thống tệp chỉ đọc có tính toàn vẹn được dm-verity bảo vệ cho mọi hoạt động đầu vào/đầu ra. Mọi hành vi giả mạo các thành phần hệ thống đều phải bị cấm hoặc có thể phát hiện được để thiết bị có thể từ chối khởi động nếu bị xâm nhập.

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

Các tệp trong một vùng chứa APEX là từ các phân vùng tích hợp sẵn (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 mức độ bảo mật cho các tệp, tất cả cá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 một cây băm và một trình 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ù cung cấp một mức độ bảo vệ nhất định chống lại hành vi giả mạo, nhưng mục đích chính của tính năng này là bảo vệ 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, sẽ không có biện pháp bảo vệ nào khác. Điều này một lần nữa cho thấy sự thụt lùi so với việc mọi thành phần hệ thống đều 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 các đường dẫn từ /system sang /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, chẳng hạn như /apex/<name>/lib/libfoo.so. Khi các tệp này nằm trong phân vùng /system, bạn có thể truy cập vào chúng thông qua các đường dẫn như /system/lib/libfoo.so. Một ứ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 các đườ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 lê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à chiếm đoạt 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ỏ lựa 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 Bionic, ứng dụng này triển khai các chức năng. Trong những trường hợp như vậy, các ứng dụng đó sẽ không được chuyển hướng.