Hỗ trợ kích thước trang 16 KB

Historically, Android has only supported 4 KB memory page sizes, which has optimized system memory performance for the average amount of total memory that Android devices have typically had. Beginning with Android 15, AOSP supports devices that are configured to use a page size of 16 KB (16 KB devices). If your app uses any NDK libraries, either directly or indirectly through an SDK, then you will need to rebuild your app for it to work on these 16 KB devices.

As device manufacturers continue to build devices with larger amounts of physical memory (RAM), many of these devices will adopt 16 KB (and eventually greater) page sizes to optimize the device's performance. Adding support for 16 KB page size devices enables your app to run on these devices and helps your app benefit from the associated performance improvements. Without recompiling, apps won't work on 16 KB devices in future Android releases.

To help you add support for your app, we've provided guidance on how to check if your app is impacted, how to rebuild your app (if applicable), and how to test your app in a 16 KB environment using emulators (including Android 15 system images for the Android Emulator).

Yêu cầu về khả năng tương thích với Google Play

Để đảm bảo ứng dụng của bạn hoạt động đúng cách trên các phiên bản Android mới nhất, tất cả ứng dụng nhắm đến Android 15 (API cấp 35) trở lên đều phải hỗ trợ kích thước trang bộ nhớ 16 KB trên thiết bị 64 bit trên Google Play. Kể từ ngày 1 tháng 2 năm 2027, nếu các bản cập nhật ứng dụng của bạn không hỗ trợ kích thước trang bộ nhớ 16 KB, bạn sẽ không thể phát hành những bản cập nhật này.

Cảnh báo của Google Play Console về việc các bản cập nhật ứng dụng phải hỗ trợ kích thước trang bộ nhớ 16 KB muộn nhất vào ngày 1 tháng 2 năm 2027
Hình 1. Cảnh báo về khả năng tương thích của Google Play Console.

Lợi ích và hiệu suất đạt được

Các thiết bị được định cấu hình với kích thước trang 16 KB sử dụng nhiều bộ nhớ hơn một chút trung bình, nhưng cũng có nhiều điểm cải tiến về hiệu suất cho cả hệ thống và ứng dụng:

  • Giảm thời gian khởi chạy ứng dụng khi hệ thống chịu áp lực về bộ nhớ: Trung bình thấp hơn 3,16%, với mức cải thiện đáng kể hơn (lên đến 30%) đối với một số ứng dụng mà chúng tôi đã thử nghiệm
  • Giảm mức tiêu thụ điện năng trong quá trình khởi chạy ứng dụng: trung bình giảm 4,56%
  • Khởi động máy ảnh nhanh hơn: trung bình khởi động nóng nhanh hơn 4,48% và khởi động nguội nhanh hơn 6,60%
  • Cải thiện thời gian khởi động hệ thống: trung bình cải thiện 8% (khoảng 950 mili giây)

Những cải tiến này dựa trên thử nghiệm ban đầu của chúng tôi và kết quả trên các thiết bị thực tế có thể sẽ khác. Chúng tôi sẽ phân tích thêm về những lợi ích có thể xảy ra đối với ứng dụng trong quá trình kiểm thử.

Kiểm tra xem ứng dụng của bạn có bị ảnh hưởng hay không

If your app uses any native code, then you should rebuild your app with support for 16 KB devices. If you are unsure if your app uses native code, you can use the APK Analyzer to identify whether any native code is present and then check the alignment of ELF segments for any shared libraries that you find. Android Studio also provides features that help you to automatically detect alignment issues.

If your app only uses code written in the Java programming language or in Kotlin, including all libraries or SDKs, then your app already supports 16 KB devices. Nevertheless, we recommend that you test your app in a 16 KB environment to verify that there are no unexpected regressions in app behavior.

Ứng dụng có sử dụng mã gốc không?

Ứng dụng của bạn sử dụng mã gốc nếu có bất kỳ trường hợp nào sau đây:

  • Ứng dụng của bạn sử dụng mã C/C++ (gốc) bất kỳ. Nếu ứng dụng dùng Android NDK, thì tức là ứng dụng đó dùng mã gốc.
  • Ứng dụng của bạn liên kết với thư viện gốc hoặc phần phụ thuộc của bên thứ ba (chẳng hạn như SDK) có sử dụng các API này.
  • Ứng dụng của bạn do một trình tạo ứng dụng bên thứ ba sử dụng các thư viện gốc trên thiết bị tạo ra.

Xác định thư viện gốc bằng Công cụ phân tích APK

Công cụ phân tích APK là công cụ giúp bạn đánh giá nhiều khía cạnh của tệp APK đã dựng. Cách kiểm tra xem ứng dụng của bạn có sử dụng mã gốc hay không (bất kể ứng dụng có tương thích với kích thước trang 16 KB hay không):

  1. Mở Android Studio, sau đó nhấp vào File > Open (Tệp > Mở) rồi chọn một dự án bất kỳ.
  2. Trên thanh trình đơn, hãy nhấp vào Build > Analyze APK... (Tạo > Phân tích APK...)

    Tuỳ chọn trong trình đơn Build của Studio để chạy Công cụ phân tích APK
  3. Chọn tệp APK mà bạn muốn phân tích.

  4. Tìm trong thư mục lib, thư mục này lưu trữ các tệp đối tượng dùng chung (.so) nếu có. Nếu có bất kỳ tệp đối tượng dùng chung nào, thì tức là ứng dụng của bạn có sử dụng mã gốc. Cột Căn chỉnh hiển thị thông báo cảnh báo cho mọi tệp có vấn đề về căn chỉnh. Nếu không có tệp đối tượng dùng chung hoặc không có thư mục lib, thì ứng dụng của bạn không sử dụng mã gốc.

    Chế độ xem Công cụ phân tích APK cho thấy các tệp đối tượng dùng chung có tồn tại

Phát hiện vấn đề về căn chỉnh bằng quy trình kiểm tra tự động

Android Studio sẽ chủ động cảnh báo nếu thư viện hoặc tệp APK tạo sẵn của bạn không tuân thủ kích thước trang 16 KB. Sử dụng công cụ Trình phân tích APK để xem xét thư viện nào cần được cập nhật hoặc liệu có cần thay đổi mã hay không.

Thông báo cảnh báo của Studio về các vấn đề liên quan đến việc căn chỉnh trong một dự án

Công cụ tìm lỗi mã nguồn trong Android Studio cũng làm nổi bật các thư viện gốc không được căn chỉnh theo kích thước 16 KB.

Cảnh báo của trình tìm lỗi mã nguồn Studio về thư viện gốc không được căn chỉnh

Kiểm tra cách căn chỉnh các phân đoạn ELF cho thư viện chia sẻ

Đối với mọi thư viện chia sẻ, hãy xác minh rằng các phân đoạn ELF của thư viện chia sẻ được căn chỉnh đúng cách bằng cách sử dụng chế độ căn chỉnh ELF 16 KB.

Sử dụng Công cụ phân tích APK (Nên dùng)

Cách dễ nhất để xác minh việc căn chỉnh các thư viện dùng chung là sử dụng Công cụ phân tích APK trong Android Studio. Nếu tệp đối tượng dùng chung không được căn chỉnh theo bội số của 16 KB, thì cột Alignment (Căn chỉnh) sẽ hiển thị thông báo cảnh báo.

Sử dụng tập lệnh check_elf_alignment.sh (Linux hoặc macOS)

Nếu đang phát triển trên Linux hoặc macOS và muốn sử dụng tập lệnh, bạn có thể dùng tập lệnh check_elf_alignment.sh:

  1. Lưu tập lệnh check_elf_alignment.sh vào một tệp.

  2. Chạy tập lệnh trên tệp APK của ứng dụng:

    check_elf_alignment.sh APK_NAME.apk
    

    Tập lệnh này sẽ xuất ra ALIGNED hoặc UNALIGNED cho tất cả thư viện chia sẻ arm64-v8a.

  3. Nếu bất kỳ thư viện chia sẻ arm64-v8a hoặc x86_64 nào là UNALIGNED, bạn cần cập nhật gói cho các thư viện đó, sau đó biên dịch lại ứng dụng và kiểm tra lại bằng cách làm theo các bước trong phần này.

Sử dụng trực tiếp các công cụ dòng lệnh

Hãy làm theo các bước sau để kiểm tra cách căn chỉnh các phân đoạn ELF bằng cách sử dụng trực tiếp các công cụ dòng lệnh:

  1. Đảm bảo bạn đã cài đặt cả Công cụ bản dựng SDK Android phiên bản 35.0.0 trở lên và Android NDK bằng Trình quản lý SDK trong Android Studio hoặc công cụ dòng lệnh sdkmanager.
  2. Trích xuất tệp APK của ứng dụng:

    Linux hoặc macOS

    unzip APK_NAME.apk -d /tmp/my_apk_out
    

    Windows (PowerShell)

    Expand-Archive -Path .\APK_NAME.apk -DestinationPath ~\tmp\my_apk_out
    
  3. Trong thư mục tạm thời mà bạn đã giải nén tệp APK, hãy kiểm tra nội dung của thư mục lib để tìm các tệp đối tượng dùng chung (.so). Đây là các tệp đối tượng dùng chung mà bạn sẽ thấy khi xác định thư viện gốc bằng Công cụ phân tích APK. Chạy lệnh sau trên mỗi tệp đối tượng dùng chung:

    Linux hoặc macOS

    SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump -p SHARED_OBJECT_FILE.so | grep LOAD
    

    Windows (PowerShell)

    SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-objdump.exe -p SHARED_OBJECT_FILE.so | Select-String -Pattern "LOAD"
    

    Trong đó, SDK_ROOT_LOCATION là đường dẫn đến thư mục mà bạn đã cài đặt SDK Android, SHARED_OBJECT_FILE là tên của tệp đối tượng dùng chung mà bạn đang kiểm tra và NDK_VERSION là phiên bản Android NDK mà bạn đã cài đặt (ví dụ: 28.0.12433566). Kết quả sẽ có dạng như sau cho mỗi tệp bạn kiểm tra:

    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**14
    LOAD off    0x0000000000042a90 vaddr 0x0000000000043a90 paddr 0x0000000000043a90 align 2**14
    LOAD off    0x0000000000046230 vaddr 0x0000000000048230 paddr 0x0000000000048230 align 2**14
    
  4. Kiểm tra các dòng đầu ra để đảm bảo rằng các phân đoạn tải không có giá trị nhỏ hơn 2**14. Nếu bất kỳ phân đoạn tải nào có giá trị 2**13, 2**12 hoặc thấp hơn, bạn sẽ cần cập nhật gói cho các thư viện đó, sau đó biên dịch lại ứng dụng và kiểm tra lại bằng cách làm theo các bước trong phần này.

  5. Tiếp theo, hãy chạy công cụ dòng lệnh zipalign trên tệp APK của ứng dụng:

    Linux hoặc macOS

    SDK_ROOT_LOCATION/Android/sdk/build-tools/35.0.0/zipalign -v -c -P 16 4 APK_NAME.apk
    

    Windows (PowerShell)

    SDK_ROOT_LOCATION\Android\sdk\build-tools\35.0.0\zipalign.exe -v -c -P 16 4 APK_NAME.apk
    

    Trong đó, SDK_ROOT_LOCATION là đường dẫn đến thư mục mà bạn đã cài đặt SDK Android và APK_NAME là tên tệp APK của ứng dụng. Dòng cuối cùng của kết quả sẽ cho biết "Verification successful" (Xác minh thành công) nếu tất cả thư viện dùng chung đều được căn chỉnh chính xác.

    Nếu không xác minh được, bạn cần phải điều chỉnh lại một số thư viện dùng chung. Do đó, bạn cần cập nhật gói cho các thư viện đó, sau đó biên dịch lại ứng dụng và kiểm tra lại bằng cách làm theo các bước trong phần này.

Kiểm tra cờ bảo mật RELRO

Để giảm thiểu các hành vi khai thác bảo mật, trình liên kết hiện đại sử dụng cờ Relocation Read-Only (RELRO) (Chỉ đọc khi di dời) để đặt các phần mục tiêu di dời (chẳng hạn như .got và .data.rel.ro) của tệp đối tượng dùng chung thành chỉ đọc sau khi hoàn tất việc di dời động. Bật cờ RELRO trong bản dựng.

Khi GNU_RELRO chia sẻ một phân đoạn LOAD có thể ghi với dữ liệu có thể ghi tiếp theo (chẳng hạn như .data hoặc .bss), địa chỉ kết thúc của phân đoạn GNU_RELRO (VirtAddr + MemSiz) phải được căn chỉnh theo 16 KB (0x4000). Nếu không, khi Trình liên kết Android đánh dấu trang 16 KB chứa phần cuối của GNU_RELRO là chỉ đọc, thì mọi dữ liệu có thể ghi liền kề nằm trong phần còn lại của cùng trang 16 KB đó cũng sẽ trở thành chỉ đọc, khiến ứng dụng gặp sự cố trong thời gian chạy kèm theo lỗi phân đoạn (SIGSEGV) khi dữ liệu đó được ghi. Điều này thường xảy ra nếu tệp .so được tạo bằng chuỗi công cụ NDK r27 trở xuống mà không bật các cờ liên quan.

Bạn có thể sử dụng Công cụ phân tích APK trong Android Studio để trực tiếp kiểm tra trạng thái căn chỉnh RELRO của các tệp đối tượng dùng chung.

Công cụ phân tích APK trong Android Studio cho thấy chế độ căn chỉnh RELRO cho thư viện gốc

Sử dụng công cụ dòng lệnh

Nếu bạn muốn kiểm tra việc căn chỉnh theo cách thủ công hoặc cần tự động hoá quy trình, hãy làm theo các bước sau:

Bước 1: Kiểm tra tiêu đề chương trình

Chạy lệnh sau trên mỗi tệp đối tượng dùng chung (trên Linux, hãy thay thế darwin-x86_64 bằng linux-x86_64):

Linux hoặc macOS

SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf -Wl SHARED_OBJECT_FILE.so | grep 'LOAD\|RELRO\|Type'

Windows (PowerShell)

SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-readelf.exe -Wl SHARED_OBJECT_FILE.so | Select-String -Pattern 'LOAD|RELRO|Type'

Chuỗi GNU_RELRO được in nếu có phân đoạn RELRO.

Bước 2: Xác minh khả năng tuân thủ 16 KB cho RELRO

Tệp đối tượng dùng chung tuân thủ kích thước trang 16 KB đối với RELRO nếu đáp ứng bất kỳ điều kiện nào sau đây:

  1. Không có phân đoạn GNU_RELRO trong tiêu đề chương trình.
  2. Địa chỉ kết thúc GNU_RELRO được căn chỉnh theo 16 KB: Tổng địa chỉ ảo (VirtAddr) và kích thước bộ nhớ (MemSiz) chia cho 16 KB (0x4000) có số dư (modulo) bằng 0: (VirtAddr + MemSiz) % 0x4000 == 0.
  3. Không có dữ liệu có thể ghi nào theo sau GNU_RELRO trong cùng một phân đoạn LOAD: GNU_RELRO chiếm toàn bộ phần còn lại của phân đoạn LOAD chứa nó (nghĩa là LOAD.VirtAddr + LOAD.MemSiz == RELRO.VirtAddr + RELRO.MemSiz), vì vậy không có phần .data hoặc .bss có thể ghi nào chia sẻ trang cuối.

Ví dụ

Các ví dụ sau đây cho thấy cách đánh giá kết quả của llvm-readelf cho các cấu hình GNU_RELRO khác nhau.

Ví dụ 1: Tệp .so không khớp (Không tuân thủ)

Trong ví dụ này, GNU_RELRO chia sẻ một phân đoạn RW LOAD với dữ liệu có thể ghi tiếp theo và địa chỉ kết thúc của phân đoạn này không phải là bội số của 0x4000:

Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD           0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x02510 0x04510 RW  0x4000
GNU_RELRO      0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x01510 R   0x1
  • Công thức: (VirtAddr + MemSiz) % 0x4000 == 0
  • Kết quả: (0xdfaf0 + 0x01510) % 0x4000 = 0xE1000 % 0x4000 == 0x1000

Vì 0x1000 không phải là 0 và phân đoạn LOAD chứa tệp này tiếp tục vượt quá 0xE1000 (kết thúc tại 0xdfaf0 + 0x04510 = 0xE4000), nên tệp .so này không tuân thủ kích thước trang 16 KB. Trên thiết bị 16 KB, trình liên kết sẽ làm tròn phạm vi bảo vệ RELRO thành ranh giới trang 16 KB, đánh dấu 0xDC000 (dấu ngắt trang trước đó) đến 0xE4000 là chỉ đọc. Tuy nhiên, các phần .data và .bss có thể ghi của tệp .so nằm trong dải ô phụ từ 0xE1000 đến 0xE4000, dẫn đến lỗi phân đoạn khi được sửa đổi trong thời gian chạy. Tạo lại tệp .so này như mô tả trong phần Biên dịch ứng dụng bằng cách sử dụng chế độ căn chỉnh ELF 16 KB.

Ví dụ 2: Tệp .so được căn chỉnh có phân đoạn RELRO (Tuân thủ)

Trong ví dụ này, phần cuối của phân đoạn GNU_RELRO nằm trên ranh giới trang 16 KB:

Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD           0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x03510 RW  0x4000
GNU_RELRO      0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x00510 0x00510 R   0x1
  • Công thức: (VirtAddr + MemSiz) % 0x4000 == 0
  • Kết quả: (0xdfaf0 + 0x00510) % 0x4000 = 0xE0000 % 0x4000 == 0

Vì phần còn lại là 0, nên biện pháp bảo vệ RELRO sẽ kết thúc hoàn toàn ở ranh giới trang 0xE0000, khiến mọi dữ liệu có thể ghi bắt đầu từ 0xE0000 không bị ảnh hưởng.

Ví dụ 3: Tệp .so không có phân đoạn RELRO (Tuân thủ)
Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD           0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x0001d8 RW  0x4000

Tệp .so này không có phân đoạn GNU_RELRO, vì vậy, các vấn đề về ranh giới trang RELRO không áp dụng (mặc dù bạn vẫn nên bật RELRO để đảm bảo bảo mật).

Ví dụ 4: GNU_RELRO trong phân đoạn LOAD riêng (Tuân thủ)

Trong ví dụ này, GNU_RELRO không kết thúc ở ranh giới 16 KB nhưng vẫn tuân thủ vì không có dữ liệu có thể ghi nào theo sau trong cùng một phân đoạn LOAD:

Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD           0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 RW  0x4000
GNU_RELRO      0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 R   0x1
  • Công thức: (VirtAddr + MemSiz) % 0x4000 == 0
  • Kết quả: (0x4680 + 0x0980) % 0x4000 = 0x5000 % 0x4000 == 0x1000

Mặc dù 0x1000 không phải là 0, nhưng tệp .so này vẫn tuân thủ kích thước trang 16 KB. Khi so sánh GNU_RELRO với phân đoạn LOAD chứa nó (mục LOAD thứ hai), bạn sẽ thấy cả hai đều bắt đầu tại VirtAddr 0x4680 và kết thúc tại cùng một địa chỉ (0x4680 + 0x0980 = 0x5000).

Vì toàn bộ phân đoạn LOAD chỉ chứa dữ liệu RELRO, nên không có dữ liệu .data hoặc .bss có thể ghi trong dải phụ từ 0x5000 đến 0x8000 (thư viện không có dữ liệu có thể ghi không phải RELRO hoặc bất kỳ phân đoạn LOAD có thể ghi nào tiếp theo đều bắt đầu trên một ranh giới trang 16 KB riêng biệt tại 0x8000 trở lên). Tính năng bảo vệ chỉ đọc của trình liên kết sẽ không trùng lặp với bất kỳ dữ liệu có thể ghi nào, vì vậy, không có lỗi phân đoạn nào xảy ra.

Tạo ứng dụng hỗ trợ thiết bị 16 KB

Nếu ứng dụng của bạn sử dụng mã gốc, hãy hoàn tất các bước nêu trong các phần sau để đảm bảo ứng dụng hỗ trợ thiết bị 16 KB:

  1. Cập nhật gói thư viện chia sẻ
  2. Biên dịch ứng dụng bằng cách căn chỉnh ELF 16 KB
  3. Sửa mã và giải quyết các vấn đề về thời gian chạy
  4. Tối ưu hoá trình phân bổ bộ nhớ tuỳ chỉnh (nếu có)
  5. Kiểm tra xem SDK có hỗ trợ 16 KB hay không

Cập nhật gói thư viện chia sẻ

Nâng cấp lên AGP phiên bản 8.5.1 trở lên và sử dụng thư viện dùng chung không nén.

Sử dụng bundletool để xác minh việc căn chỉnh tệp zip

Để xem cách căn chỉnh của gói, hãy dùng:

bundletool dump config --bundle=<my .aab>  | grep alignment

Nếu thấy PAGE_ALIGNMENT_16K, thì bạn biết rằng gói của mình yêu cầu căn chỉnh tệp zip 16 KB. Nếu bạn thấy PAGE_ALIGNMENT_4K, thì điều này hướng dẫn tệp APK được tạo từ AAB này có các tệp .so được căn chỉnh 4 KB trong tệp zip.

AGP phiên bản 8.5.1 trở lên

Thiết bị 16 KB yêu cầu các ứng dụng đi kèm với thư viện chia sẻ chưa nén phải căn chỉnh các thư viện đó theo ranh giới căn chỉnh zip 16 KB. Để làm việc này, bạn cần nâng cấp lên Trình bổ trợ Android cho Gradle (AGP) phiên bản 8.5.1 trở lên. Hãy tham khảo phần Trợ lý nâng cấp trình bổ trợ Android cho Gradle để biết thông tin chi tiết về quy trình nâng cấp.

AGP phiên bản 8.5 trở xuống

Nếu không thể nâng cấp AGP lên phiên bản 8.5.1 trở lên, thì bạn có thể chuyển sang sử dụng thư viện chia sẻ nén. Cập nhật cấu hình Gradle để Gradle nén các thư viện dùng chung khi đóng gói ứng dụng, nhằm tránh các vấn đề về cài đặt ứng dụng với các thư viện dùng chung chưa được điều chỉnh.

Groovy

Trong tệp build.gradle, hãy thêm tuỳ chọn sau:

android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging true
      }
  }
}

Kotlin

Trong tệp build.gradle.kts, hãy thêm tuỳ chọn sau:

android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging = true
      }
  }
}
AGP phiên bản 8.0 trở xuống

Nếu đang sử dụng phiên bản AGP bằng hoặc thấp hơn 8.0, bạn cũng cần tắt tuỳ chọn thư viện gốc chưa nén cho Gói ứng dụng trong tệp gradle.properties:

android.bundle.enableUncompressedNativeLibs=false

Biên dịch ứng dụng bằng cách căn chỉnh ELF 16 KB

Thiết bị 16 KB yêu cầu các phân đoạn ELF của thư viện dùng chung phải được căn chỉnh đúng cách bằng cách sử dụng chế độ căn chỉnh ELF 16 KB để ứng dụng của bạn có thể chạy.

Đối với nhà phát triển trò chơi, nếu trò chơi của bạn chạy trên công cụ phát triển trò chơi Unity, hãy tham khảo hướng dẫn về Unity. Nếu trò chơi của bạn chạy trên công cụ phát triển trò chơi Unreal, hãy tham khảo hướng dẫn về Unreal.Đối với công cụ phát triển trò chơi gốc, hãy tiếp tục xem hướng dẫn này.

Để biên dịch ứng dụng bằng cách căn chỉnh ELF 16 KB, hãy hoàn tất các bước trong một trong những phần sau, tuỳ thuộc vào phiên bản Android NDK mà bạn đang sử dụng.

Android NDK r28 trở lên

NDK phiên bản r28 trở lên biên dịch theo căn chỉnh 16 KB theo mặc định.

Android NDK r27 trở xuống

Để hỗ trợ biên dịch thư viện chia sẻ được căn chỉnh theo 16 KB bằng Android NDK phiên bản r27 trở xuống, hãy sử dụng các cờ trình liên kết sau:

-Wl,-z,max-page-size=16384
-Wl,-z,common-page-size=16384

Sau đây là cách cập nhật tệp cấu hình hệ thống xây dựng:

ndk-build

Nếu bạn đang sử dụng ndk-build, hãy cập nhật Android.mk để bật tính năng căn chỉnh ELF 16 KB:

LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384

CMake

Nếu bạn đang sử dụng CMake, hãy cập nhật CMakeLists.txt để bật tính năng căn chỉnh ELF 16 KB:

target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
    "-Wl,-z,max-page-size=16384"
    "-Wl,-z,common-page-size=16384"
)

Sửa mã và giải quyết các vấn đề về thời gian chạy

Ngay cả khi ứng dụng của bạn được căn chỉnh theo kích thước 16 KB, ứng dụng vẫn có thể gặp lỗi nếu các vị trí trong mã giả định rằng thiết bị đang sử dụng một kích thước trang cụ thể. Để tránh tình trạng này, hãy hoàn tất các bước sau:

  1. Xoá mọi phần phụ thuộc được cố định giá trị trong mã tham chiếu đến hằng số PAGE_SIZE hoặc các thực thể trong logic mã giả định rằng kích thước trang của thiết bị là 4 KB (4096).

    Thay vào đó, hãy sử dụng getpagesize() hoặc sysconf(_SC_PAGESIZE).

  2. Tìm cách sử dụng mmap() và các API khác yêu cầu đối số được căn chỉnh theo trang, đồng thời thay thế bằng các phương án thay thế nếu cần.

Trong một số trường hợp, nếu ứng dụng của bạn sử dụng PAGE_SIZE làm giá trị thuận tiện không liên kết với kích thước trang cơ bản, thì việc này sẽ không khiến ứng dụng của bạn gặp sự cố khi dùng ở chế độ 16 KB. Tuy nhiên, nếu giá trị này được truyền đến hạt nhân bằng mmap mà không có MAP_FIXED, thì hạt nhân vẫn sử dụng toàn bộ trang, gây lãng phí một số bộ nhớ. Vì những lý do này, PAGE_SIZE không được xác định khi chế độ 16 KB được bật trên NDK r27 trở lên.

Nếu ứng dụng của bạn sử dụng PAGE_SIZE theo cách này và không bao giờ trực tiếp truyền giá trị này đến hạt nhân, thì thay vì sử dụng PAGE_SIZE, hãy tạo một biến mới có tên mới để phản ánh rằng biến đó được dùng cho các mục đích khác và không phản ánh trang bộ nhớ thực.

Tối ưu hoá trình phân bổ bộ nhớ tuỳ chỉnh

Trên hệ thống 16 KB, đơn vị bộ nhớ thực nhỏ nhất mà hệ điều hành phân bổ lớn hơn 4 lần so với hệ thống 4 KB. Nếu một trình phân bổ tuỳ chỉnh được thiết kế dựa trên giả định 4 KB, thì trình phân bổ đó có thể phân tán các đối tượng nhỏ trên nhiều trang 16 KB và giữ lại bộ nhớ trống không cần thiết. Điều này có thể làm tăng đáng kể mức sử dụng bộ nhớ vật lý (RSS) và làm giảm hiệu quả của không gian hoán đổi nén (ZRAM).

Nếu mã của bạn quản lý các vùng nhớ riêng, hãy làm theo các đề xuất sau:

1. Tránh ngưỡng byte được cố định giá trị trong mã để giải phóng bộ nhớ

Nhiều trình phân bổ sử dụng giới hạn byte cố định để quyết định thời điểm giải phóng bộ nhớ trở lại hệ điều hành bằng madvise(MADV_DONTNEED) (ví dụ: chỉ giải phóng bộ nhớ nếu các đối tượng đang hoạt động chiếm ít hơn 8 KB).

Trên hệ thống 16 KB, ngay cả một đối tượng hoạt động 16 byte cũng ghim toàn bộ trang 16 KB (lớn hơn 8 KB). Do đó, ngưỡng giải phóng không bao giờ được đáp ứng và trình phân bổ không bao giờ trả lại bộ nhớ không sử dụng xung quanh cho nhân hệ điều hành.

Việc nên làm: Không bao giờ sử dụng hằng số byte được cố định giá trị trong mã cho phương pháp phỏng đoán giải phóng trang. Tự động điều chỉnh ngưỡng phát hành trong thời gian chạy dựa trên kích thước trang thực tế bằng cách sử dụng sysconf(_SC_PAGESIZE).

2. Lấp đầy các trang đã sử dụng trước (phân bổ dày đặc trước)

Nếu một trình phân bổ cấp phát bộ nhớ theo thứ tự nhập trước xuất trước (FIFO) hoặc vòng tròn, thì các lượt phân bổ mới sẽ nằm rải rác trên nhiều trang 16 KB đã được lấp đầy một phần. Một đối tượng duy nhất trên một trang sẽ giữ tất cả 16 KB trong RAM vật lý.

Việc nên làm: Luôn phân bổ các đối tượng mới từ trang hoặc phiến đầy nhất (mật độ cao nhất) trước khi chạm vào các trang trống hoặc ít được sử dụng. Việc tập trung các lượt phân bổ mới vào các trang đã bị bẩn cho phép các trang ít được sử dụng tự nhiên giảm xuống còn 0 đối tượng đang hoạt động, nhờ đó toàn bộ trang 16 KB có thể được giải phóng cho hệ điều hành.

3. Căn chỉnh vùng bộ nhớ thành 16 KB và giữ kích thước vùng ở mức vừa phải

Các dải nhiều trang được thiết kế cho hệ thống 4 KB có thể giữ lại quá nhiều bộ nhớ chưa được giải phóng trên nhân hệ điều hành 16 KB. Ngoài ra, các lớp kích thước không chia đều cho 16 KB sẽ gây ra tình trạng phân mảnh ở cuối mỗi trang.

Việc nên làm:

  • Đảm bảo tất cả vùng nhớ, ranh giới slab và căn chỉnh vùng đệm đều là bội số chính xác của kích thước trang thời gian chạy.
  • Đánh giá lại kích thước dải nhiều trang cho các lớp đối tượng nhỏ để tránh phân bổ các phiến quá lớn làm giữ lại bộ nhớ rảnh.

4. Giải phóng ngay bộ nhớ vật lý cho các vùng đệm lớn được lưu vào bộ nhớ đệm

Các trình phân bổ tuỳ chỉnh thường lưu các vùng đệm lớn (> 64 KB) vào bộ nhớ đệm trong một vùng nhớ để có thể sử dụng lại mà không phải trả chi phí hao tổn cho các lệnh gọi hệ thống mmap hoặc munmap. Tuy nhiên, việc giữ các vùng đệm bẩn này trong bộ nhớ sẽ gây lãng phí hàng megabyte RAM vật lý trong khi chờ bộ hẹn giờ loại bỏ.

Việc nên làm: Giữ dải địa chỉ bộ nhớ ảo dành riêng để sử dụng lại nhanh, nhưng hãy gọi madvise(..., MADV_DONTNEED) hoặc madvise(..., MADV_FREE) ngay lập tức khi trả lại vùng đệm cho bộ nhớ đệm. Hệ điều hành sẽ thu hồi RAM thực ngay lập tức, trong khi ứng dụng của bạn vẫn có thể sử dụng lại địa chỉ ảo ngay lập tức mà không cần phân bổ lại.

5. Xoá bộ nhớ khi giải phóng thay vì khi phân bổ để nén ZRAM

Android sử dụng bộ nhớ hoán đổi nén (ZRAM) để giữ các ứng dụng nền trong bộ nhớ. Trên thiết bị 16 KB, nếu một trang chứa dù chỉ một đối tượng đang hoạt động, thì toàn bộ trang 16 KB sẽ vẫn nằm trong bộ nhớ hoặc được hoán đổi sang ZRAM. Dữ liệu rác còn sót lại (chẳng hạn như con trỏ và chuỗi cũ) trên các phần đã giải phóng trước đó của trang đó sẽ nén kém.

Nếu trình phân bổ của bạn đặt bộ nhớ về 0 (chẳng hạn như để bảo mật hoặc phân bổ giá trị khởi tạo bằng 0), hãy cân nhắc đặt về 0 khi huỷ phân bổ (free()) thay vì khi phân bổ:

  • Các trang được ghim tốn ít chi phí hơn: Bộ nhớ rảnh trên các trang 16 KB được lấp đầy một phần sẽ nén xuống gần như không còn gì trong ZRAM, vì vậy, các trang được ghim không lãng phí không gian hoán đổi vật lý.
  • Mức hao tổn bộ nhớ đệm tối thiểu: Tại thời điểm free() được gọi, bộ nhớ đã được làm nóng trong bộ nhớ đệm CPU, tránh tình trạng thiếu bộ nhớ đệm sau này.

Tóm tắt các đề xuất

Khu vực Nội dung đề xuất Tác động dự kiến
Ngưỡng thanh lọc Tự động điều chỉnh ngưỡng phát hành bằng sysconf(_SC_PAGESIZE). Ngăn logic phát hành bị bế tắc vĩnh viễn.
Thứ tự phân bổ Phân bổ từ trang hoặc phiến dày đặc nhất (gần như đầy) trước. Giảm bộ nhớ thường trú (RSS) bằng cách đóng gói các đối tượng đang hoạt động cùng nhau.
Định cỡ span Căn chỉnh các lớp kích thước thành bội số của 16 KB và tránh các bản ghi quá lớn. Loại bỏ tình trạng phân mảnh trang cuối và giảm mức sử dụng bộ nhớ.
Bộ nhớ đệm của vùng đệm Gọi madvise(MADV_DONTNEED) ngay lập tức khi lưu vùng đệm lớn vào bộ nhớ đệm. Loại bỏ tình trạng phình RAM khi không hoạt động trong khi vẫn duy trì khả năng sử dụng lại ảo nhanh chóng.
Swap / ZRAM Xoá bộ nhớ trên free() thay vì trên quá trình phân bổ. Cải thiện tỷ lệ nén ZRAM để các trang được ghim tốn ít dung lượng hơn.

Kiểm tra xem SDK có hỗ trợ 16 KB hay không

Nhiều SDK tương thích với kích thước trang 16 KB, đặc biệt là nếu bạn tự xây dựng hoặc tải các bản dựng sẵn gần đây. Tuy nhiên, vì một số bản dựng sẵn SDK hoặc phiên bản SDK không tương thích với kích thước trang 16 KB, nên bạn cần kiểm tra trang web của từng nhà cung cấp SDK để xác định phiên bản nào nên dùng với kích thước trang 16 KB.

Thử nghiệm ứng dụng của bạn trong môi trường 16 KB

Sau khi tạo ứng dụng có hỗ trợ thiết bị 16 KB, bạn nên kiểm thử ứng dụng trong môi trường 16 KB để xem ứng dụng có gặp phải vấn đề hồi quy nào không. Để thực hiện việc này, hãy làm theo các bước sau:

  1. Thiết lập SDK Android 15 trở lên.

  2. Thiết lập một trong các môi trường thử nghiệm sau:

  3. Khởi động thiết bị thử nghiệm, sau đó chạy lệnh sau để xác minh rằng thiết bị đang sử dụng môi trường 16 KB:

    adb shell getconf PAGE_SIZE
    

    Lệnh này sẽ trả về giá trị 16384.

  4. Chạy lệnh zipalign sau đây để xác minh rằng ứng dụng của bạn được căn chỉnh theo kích thước trang 16 KB, trong đó APK_NAME là tên tệp APK của ứng dụng:

    zipalign -c -P 16 -v 4 APK_NAME.apk
    
  5. Kiểm thử kỹ lưỡng ứng dụng, tập trung vào mọi khía cạnh có thể chịu ảnh hưởng của việc thay đổi các thực thể mã tham chiếu đến kích thước trang cụ thể.

Thiết lập Trình mô phỏng Android bằng hình ảnh hệ thống dựa trên 16 KB

Để thiết lập môi trường 16 KB bằng Trình mô phỏng Android, hãy làm theo các bước sau:

  1. Trong Android Studio, hãy nhấp vào Tools > SDK Manager (Công cụ > Trình quản lý SDK).
  2. Trong thẻ SDK Platforms (Nền tảng SDK), hãy chọn Show Package Details (Hiện chi tiết gói), sau đó mở rộng phần Android VanillaIceCream trở lên rồi chọn một hoặc cả hai hình ảnh hệ thống của trình mô phỏng sau đây, tuỳ thuộc vào thiết bị ảo mà bạn muốn tạo:

    • Hình ảnh hệ thống ARM 64 v8a có kích thước trang 16 KB thử nghiệm của API Google
    • Hình ảnh hệ thống Google APIs Experimental 16 KB Page Size Intel x86_64 Atom
    Tải hình ảnh hệ thống trình mô phỏng 16 KB xuống bằng Trình quản lý SDK trong Android Studio
  3. Nhấp vào Apply > OK (Áp dụng > OK) để tải hình ảnh hệ thống mà bạn đã chọn xuống.

  4. Làm theo các bước để thiết lập thiết bị ảo cho Android 15. Khi được nhắc chọn một ảnh hệ thống, hãy chọn ảnh hệ thống 16 KB mà bạn đã tải xuống. Nếu không được đề xuất tự động, bạn có thể tìm thấy hình ảnh hệ thống 16 KB trong thẻ Hình ảnh khác.

    Tìm hình ảnh trình mô phỏng 16 KB trong thẻ Other Images (Hình ảnh khác)

Chạy trình mô phỏng

Sau khi bạn thiết lập xong Trình mô phỏng Android và thiết bị ảo, hãy chạy trình mô phỏng từ trình đơn thiết bị mục tiêu hoặc từ dòng lệnh.

Bật chế độ 16 KB trên thiết bị bằng cách sử dụng tuỳ chọn cho nhà phát triển

Bật/tắt lựa chọn cho nhà phát triển Khởi động với kích thước trang 16 KB để khởi động thiết bị ở chế độ 16 KB.

Trong các phiên bản QPR của Android 15, bạn có thể sử dụng tùy chọn cho nhà phát triển có trên một số thiết bị để khởi động thiết bị ở chế độ 16 KB và thực hiện kiểm thử trên thiết bị. Trước khi sử dụng tuỳ chọn cho nhà phát triển, hãy chuyển đến phần Cài đặt > Hệ thống > Bản cập nhật phần mềm rồi áp dụng mọi bản cập nhật hiện có.

Tuỳ chọn cho nhà phát triển này có trên các thiết bị sau:

  • Pixel 8 và 8 Pro (chạy Android 15 QPR1 trở lên)

  • Pixel 8a (chạy Android 15 QPR1 trở lên)

  • Pixel 9, 9 Pro và 9 Pro XL (chạy Android 15 QPR2 trở lên)

  • Pixel 9a (chạy Android 16 trở lên)

Chế độ tương thích ngược 16 KB

Cảnh báo ở chế độ tương thích với kích thước trang

Cảnh báo ở chế độ tương thích với kích thước trang

Bạn có thể sử dụng tuỳ chọn tương thích ngược 16 KB khi thiết bị đang chạy bằng nhân hệ điều hành 16 KB. Trình quản lý gói chạy một ứng dụng ở chế độ tương thích ngược 16 KB khi đáp ứng các điều kiện sau:

  • Nếu ứng dụng có tệp ELF (có đuôi .so) với phân đoạn LOAD căn chỉnh 4 KB.
  • Nếu tệp APK nén có các tệp ELF không nén được căn chỉnh theo ZIP 4 KB.

Nếu trình quản lý gói đã bật chế độ tương thích ngược 16 KB cho một ứng dụng, thì ứng dụng đó sẽ hiển thị cảnh báo khi khởi chạy lần đầu, cho biết rằng ứng dụng đang chạy ở chế độ tương thích ngược 16 KB.

Chế độ tương thích ngược 16 KB cho phép một số ứng dụng hoạt động, nhưng để đảm bảo độ tin cậy và ổn định cao nhất, các ứng dụng vẫn phải được căn chỉnh theo kích thước trang 16 KB.

Trên trang thông tin ứng dụng, trong phần Nâng cao, hãy bật/tắt chế độ cài đặt Chạy ứng dụng ở chế độ tương thích với kích thước trang để bật hoặc tắt chế độ tương thích ngược 16 KB cho một ứng dụng cụ thể. Chế độ cài đặt này chỉ xuất hiện khi thiết bị đang chạy với kích thước trang 16 KB.

Chế độ cài đặt tương thích với kích thước trang

Chế độ cài đặt chế độ tương thích với kích thước trang

Cách buộc bật khả năng tương thích ngược 16 KB cho mọi ứng dụng trên thiết bị:

adb shell setprop bionic.linker.16kb.app_compat.enabled true
adb shell setprop pm.16kb.app_compat.disabled false

Cách buộc tắt khả năng tương thích ngược 16 KB cho mọi ứng dụng trên thiết bị:

adb shell setprop bionic.linker.16kb.app_compat.enabled false
adb shell setprop pm.16kb.app_compat.disabled true

Trong Android 17, bạn cũng có thể buộc tắt khả năng tương thích ngược 16 KB cho mọi ứng dụng và khiến mọi tệp nhị phân không tương thích bị huỷ ngay lập tức:

    adb shell setprop bionic.linker.16kb.app_compat.enabled fatal
    adb shell setprop pm.16kb.app_compat.disabled true

Đặt thuộc tính android:pageSizeCompat thành bật hoặc tắt để bật hoặc tắt chế độ tương thích ngược cho một ứng dụng cụ thể trong AndroidManifest.xml của ứng dụng đó. Khi bạn đặt thuộc tính này, ứng dụng sẽ không hiển thị cảnh báo về chế độ tương thích ngược khi khởi chạy.