Obsługa stron o rozmiarze 16 KB

W przeszłości Android obsługiwał tylko strony pamięci o rozmiarze 4 KB, co optymalizowało wydajność pamięci systemowej pod kątem średniej ilości pamięci całkowitej, jaką zwykle miały urządzenia z Androidem. Od Androida 15 AOSP obsługuje urządzenia skonfigurowane do używania rozmiaru strony 16 KB (urządzenia 16 KB). Jeśli Twoja aplikacja używa bibliotek NDK bezpośrednio lub pośrednio za pomocą pakietu SDK, musisz ją przebudować, aby działała na urządzeniach o rozmiarze strony 16 KB.

Producenci urządzeń nadal tworzą urządzenia z większą ilością pamięci fizycznej (RAM). Wiele z nich będzie korzystać ze stron o rozmiarze 16 KB (a w przyszłości większych), aby zoptymalizować wydajność urządzenia. Dodanie obsługi urządzeń ze stronami o rozmiarze 16 KB umożliwia uruchamianie aplikacji na tych urządzeniach i korzystanie z powiązanych z tym ulepszeń wydajności. Bez ponownej kompilacji aplikacje nie będą działać na urządzeniach 16 KB w przyszłych wersjach Androida.

Aby pomóc Ci dodać obsługę w aplikacji, przygotowaliśmy wskazówki dotyczące sprawdzania, czy zmiana ma wpływ na Twoją aplikację, ponownego jej kompilowania (w odpowiednich przypadkach) oraz testowania aplikacji w środowisku 16 KB przy użyciu emulatorów (w tym obrazów systemu Android 15 dla emulatora Androida).

Wymagania dotyczące zgodności z Google Play

Z myślą o zapewnieniu prawidłowego działania aplikacji w najnowszych wersjach Androida wszystkie aplikacje kierowane na Androida 15 (API na poziomie 35) i nowsze wersje muszą obsługiwać strony pamięci o rozmiarze 16 KB na urządzeniach 64-bitowych w Google Play. Od 1 lutego 2027 r. nie będziesz mieć możliwości publikowania aktualizacji aplikacji, które nie obsługują stron pamięci o rozmiarze 16 KB.

Ostrzeżenie w Konsoli Google Play, że do 1 lutego 2027 r. aktualizacje aplikacji muszą obsługiwać strony pamięci o rozmiarze 16 KB
Rysunek 1. Ostrzeżenie o zgodności w Konsoli Google Play.

Korzyści i wzrost skuteczności

Urządzenia skonfigurowane z użyciem stron o rozmiarze 16 KB zużywają średnio nieco więcej pamięci, ale zyskują też różne ulepszenia wydajności zarówno systemu, jak i aplikacji:

  • Krótszy czas uruchamiania aplikacji, gdy system jest pod presją pamięci: średnio o 3,16% krótszy, a w przypadku niektórych testowanych aplikacji o znacznie więcej (do 30%).
  • Zmniejszone zużycie energii podczas uruchamiania aplikacji: średnio o 4,56%
  • Szybsze uruchamianie aparatu: średnio o 4,48% szybsze uruchomienia z pamięci i o 6,60% szybsze uruchomienia „na zimno”
  • Skrócony czas uruchamiania systemu: skrócenie o 8% (około 950 milisekund)

Te ulepszenia bazują na naszych wstępnych testach. Wyniki na rzeczywistych urządzeniach mogą się różnić. W miarę kontynuowania testów będziemy przeprowadzać dodatkową analizę potencjalnych zysków w przypadku aplikacji.

Sprawdzanie, czy problem dotyczy Twojej aplikacji

Jeśli Twoja aplikacja używa kodu natywnego, zrekompiluj ją, aby obsługiwała urządzenia 16-kilobajtowe. Jeśli nie masz pewności, czy Twoja aplikacja używa kodu natywnego, możesz za pomocą narzędzia APK Analyzer sprawdzić, czy zawiera ona kod natywny, a następnie sprawdzić wyrównanie segmentów ELF w przypadku znalezionych bibliotek współdzielonych. Android Studio udostępnia też funkcje, które pomagają automatycznie wykrywać problemy z wyrównaniem.

Jeśli Twoja aplikacja używa tylko kodu napisanego w języku programowania Java lub Kotlin, w tym wszystkich bibliotek i pakietów SDK, to już obsługuje urządzenia 16-bitowe. Zalecamy jednak przetestowanie aplikacji w środowisku 16 KB, aby sprawdzić, czy nie występują nieoczekiwane regresje w jej działaniu.

Czy Twoja aplikacja używa kodu natywnego?

Aplikacja używa kodu natywnego, jeśli:

  • Twoja aplikacja używa kodu C/C++ (natywnego). Jeśli Twoja aplikacja używa Android NDK, oznacza to, że używa kodu natywnego.
  • Twoja aplikacja jest połączona z zewnętrznymi bibliotekami natywnymi lub zależnościami (np. pakietami SDK), które ich używają.
  • Twoja aplikacja została utworzona za pomocą kreatora aplikacji innej firmy, który używa bibliotek natywnych na urządzeniu.

Identyfikowanie bibliotek natywnych za pomocą narzędzia APK Analyzer

APK Analyzer to narzędzie, które umożliwia ocenę różnych aspektów skompilowanego pliku APK. Aby sprawdzić, czy aplikacja używa kodu natywnego (niezależnie od tego, czy jest zgodna z trybem 16 KB):

  1. Otwórz Android Studio, a następnie kliknij Plik > Otwórz i wybierz dowolny projekt.
  2. Na pasku menu kliknij Build > Analyze APK… (Kompilacja > Analizuj plik APK…).

    Opcja menu Studio Build do uruchamiania narzędzia APK Analyzer
  3. Wybierz plik APK, który chcesz przeanalizować.

  4. Zajrzyj do folderu lib, który zawiera pliki obiektów udostępnionych (.so), jeśli takie istnieją. Jeśli są obecne jakieś pliki obiektów udostępnionych, aplikacja używa kodu natywnego. W kolumnie Wyrównanie wyświetlane są komunikaty ostrzegawcze dotyczące plików, które mają problemy z wyrównaniem. Jeśli nie ma żadnych udostępnionych plików obiektów ani folderu lib, aplikacja nie używa kodu natywnego.

    Widok narzędzia APK Analyzer pokazujący, że pliki obiektów współdzielonych są obecne

Wykrywanie problemów z wyrównaniem za pomocą automatycznych kontroli

Android Studio ostrzega, jeśli gotowe biblioteki lub pliki APK nie są zgodne z rozmiarem 16 KB. Użyj narzędzia APK Analyzer, aby sprawdzić, które biblioteki wymagają aktualizacji lub czy konieczne są zmiany w kodzie.

Powiadomienia z ostrzeżeniami w Studio dotyczące problemów z wyrównaniem w projekcie

Lint w Android Studio wyróżnia też biblioteki natywne, które nie są zgodne z trybem 16 KB.

Ostrzeżenie lintera w Studio dotyczące niewyrównanej biblioteki natywnej

Sprawdzanie wyrównania segmentów ELF w przypadku bibliotek współdzielonych

W przypadku bibliotek udostępnionych sprawdź, czy ich segmenty ELF są prawidłowo wyrównane przy użyciu wyrównania ELF 16 KB.

Używanie narzędzia APK Analyzer (zalecane)

Najprostszym sposobem na sprawdzenie wyrównania bibliotek udostępnionych jest użycie narzędzia APK Analyzer w Android Studio. Jeśli plik obiektu udostępnionego nie jest wyrównany do 16 KB, w kolumnie Wyrównanie pojawi się komunikat ostrzegawczy.

Używanie skryptu check_elf_alignment.sh (Linux lub macOS)

Jeśli tworzysz aplikacje na Linuksie lub macOS i wolisz używać skryptu, możesz użyć skryptu check_elf_alignment.sh:

  1. Zapisz skrypt check_elf_alignment.sh w pliku.

  2. Uruchom skrypt na pliku APK aplikacji:

    check_elf_alignment.sh APK_NAME.apk
    

    Skrypt zwraca wartość ALIGNED lub UNALIGNED dla wszystkich zasobów wspólnych arm64-v8a.

  3. Jeśli jakieś biblioteki współdzielone arm64-v8a lub x86_64 są UNALIGNED, musisz zaktualizować ich pakiety, a następnie ponownie skompilować aplikację i powtórzyć testy, wykonując czynności opisane w tej sekcji.

Bezpośrednie używanie narzędzi wiersza poleceń

Aby sprawdzić wyrównanie segmentów ELF za pomocą narzędzi wiersza poleceń, wykonaj te czynności:

  1. Upewnij się, że narzędzia Android SDK Build-Tools w wersji 35.0.0 lub nowszej oraz Android NDK są zainstalowane za pomocą SDK Managera w Android Studio lub narzędzia wiersza poleceń sdkmanager.
  2. Wyodrębnij plik APK aplikacji:

    Linux lub macOS

    unzip APK_NAME.apk -d /tmp/my_apk_out
    

    Windows (PowerShell)

    Expand-Archive -Path .\APK_NAME.apk -DestinationPath ~\tmp\my_apk_out
    
  3. W katalogu tymczasowym, do którego został wyodrębniony plik APK, sprawdź zawartość katalogu lib pod kątem plików obiektów udostępnionych (.so). Są to te same pliki obiektów udostępnionych, które można zobaczyć podczas identyfikowania bibliotek natywnych za pomocą narzędzia APK Analyzer. Uruchom to polecenie w przypadku każdego udostępnionego pliku obiektu:

    Linux lub 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"
    

    SDK_ROOT_LOCATION to ścieżka do katalogu, w którym zainstalowano pakiet Android SDK, SHARED_OBJECT_FILE to nazwa sprawdzanego pliku obiektu udostępnionego, a NDK_VERSION to wersja zainstalowanego pakietu Android NDK (np. 28.0.12433566). Dane wyjściowe dla każdego sprawdzanego pliku będą wyglądać mniej więcej tak:

    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. Sprawdź wiersze wyjściowe, aby upewnić się, że segmenty obciążenia nie mają wartości mniejszych niż 2**14. Jeśli którykolwiek z segmentów wczytywania ma wartość 2**13, 2**12 lub niższą, musisz zaktualizować pakiety tych bibliotek, a następnie ponownie skompilować aplikację i przetestować ją, wykonując czynności opisane w tej sekcji.

  5. Następnie uruchom narzędzie wiersza poleceń zipalign na pliku APK aplikacji:

    Linux lub 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
    

    Gdzie SDK_ROOT_LOCATION to ścieżka do katalogu, w którym zainstalowano pakiet Android SDK, a APK_NAME to nazwa pliku APK aplikacji. Jeśli wszystkie zasoby wspólne są prawidłowo wyrównane, ostatni wiersz danych wyjściowych będzie zawierał komunikat „Verification successful” (Weryfikacja zakończona powodzeniem).

    Jeśli weryfikacja się nie powiedzie, niektóre biblioteki współdzielone trzeba będzie ponownie wyrównać. W takim przypadku musisz zaktualizować pakowanie tych bibliotek, a następnie ponownie skompilować aplikację i jeszcze raz ją przetestować, wykonując czynności opisane w tej sekcji.

Sprawdzanie flagi zabezpieczeń RELRO

Aby ograniczyć luki w zabezpieczeniach, nowoczesne linkery używają flagi Relocation Read-Only (RELRO), aby po zakończeniu dynamicznych relokacji sekcje docelowe relokacji (takie jak .got i .data.rel.ro) udostępnionego pliku obiektowego były tylko do odczytu. Włącz flagę RELRO w kompilacji.

Gdy GNU_RELRO udostępnia zapisywalny segment LOAD z kolejnymi zapisywalnymi danymi (takimi jak .data lub .bss), adres końcowy segmentu GNU_RELRO (VirtAddr + MemSiz) musi być wyrównany do 16 KB (0x4000). W przeciwnym razie, gdy Android Linker oznaczy stronę 16 KB zawierającą koniec GNU_RELRO jako tylko do odczytu, wszystkie sąsiadujące zapisywalne dane znajdujące się w pozostałej części tej samej strony 16 KB również staną się tylko do odczytu, co spowoduje awarię aplikacji w czasie działania z błędem segmentacji (SIGSEGV), gdy te dane zostaną zapisane. Zwykle dzieje się tak, gdy plik .so został utworzony za pomocą łańcucha narzędzi NDK w wersji 27 lub starszej bez włączenia odpowiednich flag.

Możesz użyć narzędzia APK Analyzer w Android Studio, aby bezpośrednio sprawdzić stan wyrównania RELRO plików obiektów udostępnionych.

Narzędzie APK Analyzer w Android Studio pokazujące dopasowanie RELRO w przypadku bibliotek natywnych

Używanie narzędzi wiersza poleceń

Jeśli wolisz sprawdzić dopasowanie ręcznie lub chcesz zautomatyzować ten proces, wykonaj te czynności:

Krok 1. Sprawdź nagłówki programu

Uruchom to polecenie w przypadku każdego udostępnionego pliku obiektu (w systemie Linux zastąp darwin-x86_64 ciągiem linux-x86_64):

Linux lub 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'

Jeśli istnieje segment RELRO, wyświetlany jest ciąg GNU_RELRO.

Krok 2. Sprawdź zgodność RELRO z rozmiarem 16 KB

Plik obiektu udostępnionego jest zgodny z rozmiarem 16 KB w odniesieniu do RELRO, jeśli spełniony jest dowolny z tych warunków:

  1. Brak segmentu GNU_RELRO w nagłówkach programu.
  2. Adres końcowy GNU_RELRO jest wyrównany do 16 KB: suma adresu wirtualnego (VirtAddr) i rozmiaru pamięci (MemSiz) podzielona przez 16 KB (0x4000) daje resztę (modulo) równą 0: (VirtAddr + MemSiz) % 0x4000 == 0.
  3. Po GNU_RELRO w tym samym segmencie LOAD nie ma żadnych danych do zapisu: GNU_RELRO zajmuje całą pozostałą część zawierającego go segmentu LOAD (czyli LOAD.VirtAddr + LOAD.MemSiz == RELRO.VirtAddr + RELRO.MemSiz), więc żadne sekcje .data ani .bss do zapisu nie współdzielą strony końcowej.

Przykłady

Poniższe przykłady pokazują, jak oceniać dane wyjściowe llvm-readelf w przypadku różnych konfiguracji GNU_RELRO.

Przykład 1. Plik .so niedopasowany do domeny (niezgodny z zasadami)

W tym przykładzie GNU_RELRO udostępnia segment RW LOAD z kolejnymi danymi do zapisu, a jego adres końcowy nie jest wielokrotnością 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
  • Formuła: (VirtAddr + MemSiz) % 0x4000 == 0
  • Wynik: (0xdfaf0 + 0x01510) % 0x4000 = 0xE1000 % 0x4000 == 0x1000

Ponieważ wartość 0x1000 nie jest równa 0, a zawierający go segment LOAD wykracza poza 0xE1000 (kończy się na 0xdfaf0 + 0x04510 = 0xE4000), ten plik .so nie jest zgodny z rozmiarem 16 KB. Na urządzeniu z rozmiarem strony 16 KB linker zaokrągla zakres ochrony RELRO do granic strony 16 KB, oznaczając zakres od 0xDC000 (poprzedni podział strony) do 0xE4000 jako tylko do odczytu. Jednak sekcje .data i .bss pliku .so, w których można zapisywać dane, znajdują się w podzakresie od 0xE1000 do 0xE4000, co powoduje błąd segmentacji, gdy są modyfikowane w czasie działania. Skompiluj ponownie ten plik .so zgodnie z opisem w sekcji Kompilowanie aplikacji z użyciem wyrównania ELF 16 KB.

Przykład 2. Wyrównany plik .so z segmentem RELRO (zgodny)

W tym przykładzie koniec segmentu GNU_RELRO przypada na granicę strony 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
  • Formuła: (VirtAddr + MemSiz) % 0x4000 == 0
  • Wynik: (0xdfaf0 + 0x00510) % 0x4000 = 0xE0000 % 0x4000 == 0

Reszta wynosi 0, więc ochrona RELRO kończy się dokładnie na granicy strony 0xE0000, pozostawiając dane do zapisu zaczynające się od 0xE0000 bez zmian.

Przykład 3. Plik .so bez segmentu RELRO (zgodny)
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

Ten plik .so nie ma segmentu GNU_RELRO, więc problemy z granicą strony RELRO nie mają zastosowania (choć ze względów bezpieczeństwa nadal zalecamy włączenie RELRO).

Przykład 4. GNU_RELRO we własnym segmencie LOAD (zgodne z zasadami)

W tym przykładzie GNU_RELRO nie kończy się na granicy 16 KB, ale nadal jest zgodny, ponieważ w tym samym segmencie LOAD nie ma po nim żadnych danych do zapisu:

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
  • Formuła: (VirtAddr + MemSiz) % 0x4000 == 0
  • Wynik: (0x4680 + 0x0980) % 0x4000 = 0x5000 % 0x4000 == 0x1000

Mimo że wartość 0x1000 nie jest równa 0, ten plik .so jest zgodny z trybem 16 KB. Porównanie GNU_RELRO z zawierającym go segmentem LOAD (drugi wpis LOAD) pokazuje, że oba zaczynają się o VirtAddr 0x4680 i kończą pod tym samym adresem (0x4680 + 0x0980 = 0x5000).

Ponieważ cały segment LOAD zawiera tylko dane RELRO, w podzakresie od 0x5000 do 0x8000 nie ma danych .data ani .bss, które można zapisać (biblioteka nie zawiera danych do zapisu innych niż RELRO lub każdy kolejny segment LOAD do zapisu zaczyna się na osobnej granicy strony 16 KB w 0x8000 lub dalej). Ochrona konsolidatora tylko do odczytu nie będzie nakładać się na żadne dane do zapisu, więc nie wystąpi błąd segmentacji.

Tworzenie aplikacji z obsługą urządzeń ze stronami pamięci 16 KB

Jeśli aplikacja używa kodu natywnego, wykonaj czynności opisane w poniższych sekcjach, aby upewnić się, że obsługuje ona urządzenia z rozmiarem strony 16 KB:

  1. Aktualizowanie pakietów bibliotek udostępnionych
  2. Kompilowanie aplikacji z użyciem wyrównania ELF 16 KB
  3. Poprawianie kodu i rozwiązywanie problemów ze środowiskiem wykonawczym
  4. Optymalizacja niestandardowych alokatorów pamięci (jeśli dotyczy)
  5. Sprawdzanie, czy pakiety SDK obsługują strony pamięci o rozmiarze 16 KB

Aktualizowanie pakietów bibliotek udostępnionych

Przejdź na wtyczkę Androida do obsługi Gradle w wersji 8.5.1 lub nowszej i używaj nieskompresowanych bibliotek współdzielonych.

Użyj bundletool, aby sprawdzić wyrównanie zamka błyskawicznego

Aby sprawdzić wyrównanie pakietu, użyj:

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

Jeśli widzisz PAGE_ALIGNMENT_16K, oznacza to, że pakiet wymaga wyrównania do 16 KB. Jeśli widzisz PAGE_ALIGNMENT_4K, oznacza to, że plik APK utworzony z tego pakietu AAB ma w pliku ZIP pliki .so wyrównane do 4 KB.

Wtyczka Androida do obsługi Gradle w wersji 8.5.1 lub nowszej

Urządzenia z pamięcią 16 KB wymagają, aby programy APP dostarczane z nieskompresowanymi zasobami wspólnymi były wyrównane do granicy 16 KB w pliku ZIP. Aby to zrobić, musisz uaktualnić wtyczkę Androida do obsługi Gradle (AGP) do wersji 8.5.1 lub nowszej. Szczegółowe informacje o procesie uaktualniania znajdziesz w sekcji Asystent uaktualniania wtyczki Android do Gradle.

AGP w wersji 8.5 lub starszej,

Jeśli nie możesz uaktualnić AGP do wersji 8.5.1 lub nowszej, możesz zamiast tego przejść na skompresowane biblioteki udostępnione. Zaktualizuj konfigurację Gradle, aby Gradle kompresował biblioteki udostępnione podczas pakowania aplikacji. Pozwoli to uniknąć problemów z instalacją aplikacji związanych z niewyrównanymi bibliotekami udostępnionymi.

Dynamiczny

W pliku build.gradle dodaj tę opcję:

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

Kotlin

W pliku build.gradle.kts dodaj tę opcję:

android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging = true
      }
  }
}
AGP w wersji 8.0 lub starszej,

Jeśli używasz AGP w wersji 8.0 lub starszej, musisz też wyłączyć opcję nieskompresowanej biblioteki natywnej w przypadku pakietów aplikacji w pliku gradle.properties:

android.bundle.enableUncompressedNativeLibs=false

Skompiluj aplikację, używając wyrównania ELF 16 KB

Urządzenia z 16 KB wymagają, aby segmenty ELF bibliotek współdzielonych były prawidłowo wyrównane przy użyciu wyrównania ELF 16 KB, aby aplikacja mogła działać.

Jeśli jesteś deweloperem gier i Twoja gra działa na silniku gry Unity, zapoznaj się z przewodnikiem po Unity. Jeśli Twoja gra działa na silniku Unreal, zapoznaj się z przewodnikiem po Unreal.Jeśli używasz natywnego silnika gry, kontynuuj czytanie tego przewodnika.

Aby skompilować aplikację pod kątem obsługi stron 16 KB przez pliki ELF, wykonaj czynności opisane w jednej z poniższych sekcji, w zależności od używanej wersji Android NDK.

Android NDK r28 lub nowszy

NDK w wersji r28 i nowszych domyślnie kompiluje kod pod kątem obsługi stron 16 KB.

Android NDK r27 i starsze wersje

Aby umożliwić kompilowanie bibliotek współdzielonych dostosowanych do stron 16 KB za pomocą Android NDK w wersji r27 lub starszej, użyj tych flag linkera:

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

Aby zaktualizować pliki konfiguracji systemu kompilacji:

ndk-build

Jeśli używasz ndk-build, zaktualizuj plik Android.mk, aby włączyć wyrównanie ELF do 16 KB:

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

CMake

Jeśli używasz CMake, zaktualizuj plik CMakeLists.txt, aby włączyć wyrównanie ELF do 16 KB:

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

Poprawianie kodu i rozwiązywanie problemów ze środowiskiem wykonawczym

Nawet jeśli aplikacja jest dostosowana do stron o rozmiarze 16 KB, mogą w niej występować błędy, jeśli w kodzie zakłada się, że urządzenie używa określonego rozmiaru strony. Aby uniknąć takich sytuacji, wykonaj te czynności:

  1. Usuń wszystkie zakodowane na stałe zależności, które odwołują się do stałej PAGE_SIZE, lub instancje w logice kodu, które zakładają, że rozmiar strony urządzenia wynosi 4 KB (4096).

    Zamiast tego użyj getpagesize() lub sysconf(_SC_PAGESIZE).

  2. Poszukaj zastosowań mmap() i innych interfejsów API, które wymagają argumentów wyrównanych do strony, i w razie potrzeby zastąp je alternatywnymi rozwiązaniami.

W niektórych przypadkach, jeśli aplikacja używa wartości PAGE_SIZE jako wygodnej wartości, która nie jest powiązana z rozmiarem strony, nie spowoduje to awarii aplikacji w trybie 16 KB. Jeśli jednak ta wartość zostanie przekazana do jądra za pomocą mmap bez MAP_FIXED, jądro nadal będzie używać całej strony, co spowoduje marnowanie pamięci. Z tych powodów zmienna PAGE_SIZE jest niezdefiniowana, gdy tryb 16 KB jest włączony w NDK r27 lub nowszym.

Jeśli Twoja aplikacja używa PAGE_SIZE w ten sposób i nigdy nie przekazuje tej wartości bezpośrednio do jądra, zamiast PAGE_SIZE utwórz nową zmienną o nowej nazwie, aby odzwierciedlić, że jest używana do innych celów i nie odzwierciedla rzeczywistej strony pamięci.

Optymalizacja niestandardowych alokatorów pamięci

W systemach 16 KB najmniejsza jednostka pamięci fizycznej przydzielana przez system operacyjny jest 4 razy większa niż w systemach 4 KB. Jeśli niestandardowy alokator został zaprojektowany z uwzględnieniem założeń dotyczących 4 KB, może rozpraszać małe obiekty na wielu stronach 16 KB i niepotrzebnie zachowywać pustą pamięć. Może to znacznie zwiększyć wykorzystanie pamięci fizycznej (RSS) i obniżyć wydajność skompresowanej przestrzeni wymiany (ZRAM).

Jeśli Twój kod zarządza własnymi pulami pamięci, postępuj zgodnie z tymi zaleceniami:

1. Unikaj zakodowanych na stałe progów bajtów do zwolnienia pamięci

Wiele alokatorów używa stałych limitów bajtów, aby określić, kiedy zwolnić pamięć z powrotem do systemu operacyjnego za pomocą madvise(MADV_DONTNEED) (na przykład zwalniaj pamięć tylko wtedy, gdy aktywne obiekty zajmują mniej niż 8 KB).

W systemie 16 KB nawet pojedynczy aktywny obiekt o rozmiarze 16 bajtów przypina całą stronę 16 KB (która jest większa niż 8 KB). W rezultacie próg zwolnienia nigdy nie zostaje osiągnięty, a alokator nigdy nie zwraca otaczającej nieużywanej pamięci do jądra.

Co zrobić: nigdy nie używaj zakodowanych na stałe stałych bajtowych w heurystyce zwalniania stron. Dynamicznie skaluj progi zwalniania w czasie działania na podstawie rzeczywistego rozmiaru strony za pomocą sysconf(_SC_PAGESIZE).

2. Najpierw wypełniaj już używane strony (alokacja „dense-first”)

Jeśli alokator przydziela pamięć w kolejności FIFO lub cyklicznej, nowe alokacje są rozproszone na wielu częściowo wypełnionych stronach o rozmiarze 16 KB. Pojedynczy obiekt na stronie przechowuje wszystkie 16 KB w fizycznej pamięci RAM.

Co zrobić: zawsze przydzielaj nowe obiekty z najbardziej zapełnionej (najgęstszej) strony lub płyty, zanim dotkniesz pustych lub rzadko używanych stron. Koncentrowanie nowych przydziałów na już zapełnionych stronach pozwala na naturalne opróżnianie rzadko używanych stron do zera aktywnych obiektów, dzięki czemu cała strona o rozmiarze 16 KB może zostać zwolniona i przekazana systemowi operacyjnemu.

3. Dopasuj pule pamięci do 16 KB i utrzymuj umiarkowane rozmiary pul

Wielostronicowe zakresy zaprojektowane dla systemów 4 KB mogą zawierać nadmierną ilość niezwolnionej pamięci w jądrach 16 KB. Dodatkowo klasy rozmiaru, które nie dzielą się równomiernie na 16 KB, powodują fragmentację na końcu każdej strony.

Co zrobić:

  • Upewnij się, że wszystkie pule pamięci, granice bloków i wyrównania buforów są dokładnymi wielokrotnościami rozmiaru strony środowiska wykonawczego.
  • Ponownie oceń rozmiary zakresów wielostronicowych dla klas małych obiektów, aby uniknąć przydzielania zbyt dużych bloków, które blokują nieużywaną pamięć.

4. Natychmiastowe zwalnianie pamięci fizycznej na potrzeby dużych buforów w pamięci podręcznej

Niestandardowe alokatory często przechowują duże buforu (> 64 KB) w puli w pamięci, aby można było ich używać ponownie bez obciążenia związanego z wywołaniami systemowymi mmap lub munmap. Jednak przechowywanie tych nieaktualnych buforów w pamięci marnuje megabajty fizycznej pamięci RAM podczas oczekiwania na licznik czasu wykluczenia.

Co zrobić: zachowaj zakres adresów pamięci wirtualnej zarezerwowany do szybkiego ponownego użycia, ale wywołaj madvise(..., MADV_DONTNEED) lub madvise(..., MADV_FREE) natychmiast po zwróceniu bufora do pamięci podręcznej. System operacyjny natychmiast odzyskuje fizyczną pamięć RAM, a aplikacja może od razu ponownie użyć adresu wirtualnego bez konieczności ponownego przydzielania.

5. Zerowanie pamięci przy zwalnianiu zamiast przy przydzielaniu na potrzeby kompresji ZRAM

Android używa skompresowanej przestrzeni wymiany (ZRAM), aby przechowywać aplikacje działające w tle w pamięci. Na urządzeniach z rozmiarem strony 16 KB, jeśli strona zawiera choćby 1 aktywny obiekt, cała strona 16 KB pozostaje w pamięci lub jest przenoszona do ZRAM. Pozostałe niepotrzebne dane (takie jak nieaktualne wskaźniki i ciągi znaków) w uprzednio zwolnionych częściach tej strony słabo się kompresują.

Jeśli alokator zeruje pamięć (np. ze względów bezpieczeństwa lub w przypadku alokacji zainicjowanych zerem), rozważ zerowanie przy cofaniu alokacji (free()), a nie przy alokacji:

  • Przypięte strony kosztują mniej: nieużywana pamięć na częściowo wypełnionych stronach o rozmiarze 16 KB jest kompresowana w ZRAM do niemal zera, więc przypięte strony nie marnują fizycznej przestrzeni wymiany.
  • Minimalne obciążenie pamięci podręcznej: w momencie wywołania funkcji free() pamięć jest już aktywna w pamięci podręcznej procesora, co pozwala uniknąć dodatkowych braków danych w pamięci podręcznej w późniejszym czasie.

Podsumowanie rekomendacji

Obszar Rekomendacja Oczekiwany wpływ
Progi usuwania Dynamicznie skaluj progi wersji za pomocą sysconf(_SC_PAGESIZE). Zapobiega trwałemu blokowaniu logiki wersji.
Kolejność alokacji Przydziel najpierw z najgęstszej (prawie pełnej) strony lub płyty. Zmniejsza pamięć rezydentną (RSS) przez pakowanie aktywnych obiektów.
Rozmiar rozpiętości Dopasuj klasy rozmiarów do wielokrotności 16 KB i unikaj zbyt dużych bloków. Eliminuje fragmentację stron końcowych i zmniejsza marnowanie pamięci.
Pamięci podręczne buforów Podczas buforowania dużych buforów natychmiast wywołaj madvise(MADV_DONTNEED). Eliminuje niepotrzebne wykorzystanie pamięci RAM, zachowując jednocześnie szybkie ponowne użycie pamięci wirtualnej.
Swap / ZRAM Zeruj pamięć na free() zamiast przy alokacji. Poprawia współczynniki kompresji ZRAM, dzięki czemu przypięte strony zajmują mniej miejsca.

Sprawdzanie, czy pakiety SDK obsługują strony pamięci o rozmiarze 16 KB

Wiele pakietów SDK jest zgodnych ze stronami pamięci o rozmiarze 16 KB, zwłaszcza jeśli tworzysz je samodzielnie lub korzystasz z najnowszych gotowych wersji. Niektóre gotowe pakiety SDK lub wersje pakietów SDK nie są jednak zgodne z rozmiarem 16 KB, dlatego sprawdź na stronie każdego dostawcy pakietu SDK, której wersji należy używać z rozmiarem 16 KB.

Przetestuj aplikację w środowisku 16 KB

Po utworzeniu aplikacji z obsługą urządzeń ze stronami pamięci 16 KB przetestuj ją w środowisku 16 KB, aby sprawdzić, czy wrażenia użytkownika aplikacji nie uległy pogorszeniu. W tym celu wykonaj następujące czynności:

  1. Skonfiguruj pakiet SDK Androida 15 lub nowszy.

  2. Skonfiguruj jedno z tych środowisk testowych:

  3. Uruchom urządzenie testowe, a następnie wykonaj to polecenie, aby sprawdzić, czy używa ono środowiska 16 KB:

    adb shell getconf PAGE_SIZE
    

    Polecenie powinno zwrócić wartość 16384.

  4. Uruchom to polecenie zipalign, aby sprawdzić, czy aplikacja jest wyrównana do 16 KB (APK_NAME to nazwa pliku APK aplikacji):

    zipalign -c -P 16 -v 4 APK_NAME.apk
    
  5. Dokładnie przetestuj aplikację, zwracając szczególną uwagę na obszary, na które mogą mieć wpływ zmiany instancji kodu odwołujących się do określonych rozmiarów stron.

Konfigurowanie narzędzia Android Emulator z obrazem systemu opartym na 16 KB

Aby skonfigurować środowisko 16 KB za pomocą emulatora Androida, wykonaj te czynności:

  1. W Android Studio kliknij Narzędzia > Menedżer SDK.
  2. Na karcie Platformy SDK wybierz Pokaż szczegóły pakietu, a następnie rozwiń sekcję Android VanillaIceCream lub nowszą i wybierz jeden z tych obrazów systemu emulatora lub oba, w zależności od tego, jakie urządzenia wirtualne chcesz utworzyć:

    • Obraz systemu interfejsów API Google Experimental 16 KB Page Size ARM 64 v8a
    • Obraz systemu interfejsy API Google Experimental 16 KB Page Size Intel x86_64 Atom System Image
    Pobieranie obrazów systemu emulatora 16 KB za pomocą Menedżera SDK w Android Studio
  3. Kliknij Apply > OK (Zastosuj > OK), aby pobrać wybrane obrazy systemu.

  4. Wykonaj czynności opisane w sekcji Konfigurowanie urządzenia wirtualnego z Androidem 15, a gdy pojawi się prośba o wybranie obrazu systemu, wybierz pobrany obraz systemu 16 KB. Jeśli nie jest on zalecany automatycznie, obraz systemu 16 KB znajdziesz na karcie Inne obrazy.

    Znajdź obraz emulatora 16 KB na karcie Inne obrazy.

Uruchamianie emulatora

Po zakończeniu konfigurowania narzędzia Android Emulator i urządzeń wirtualnych uruchom emulator z menu urządzenia docelowego lub z wiersza poleceń.

Włączanie trybu 16 KB na urządzeniu za pomocą opcji programisty

Włącz opcję programisty Uruchom ze stroną 16 KB, aby uruchomić urządzenie w trybie 16 KB.

W wersjach QPR Androida 15 możesz skorzystać z opcji dla programistów dostępnej na niektórych urządzeniach, aby uruchomić urządzenie w trybie 16 KB i przeprowadzić testowanie na urządzeniu. Zanim skorzystasz z opcji programisty, otwórz Ustawienia > System > Aktualizacje oprogramowania i zastosuj wszystkie dostępne aktualizacje.

Ta opcja dla programistów jest dostępna na tych urządzeniach:

  • Pixel 8 i 8 Pro (z Androidem 15 QPR1 lub nowszym)

  • Pixel 8a (z Androidem 15 QPR1 lub nowszym)

  • Pixel 9, 9 Pro i 9 Pro XL (z Androidem 15 QPR2 lub nowszym)

  • Pixel 9a (z Androidem 16 lub nowszym)

Tryb zgodności wstecznej 16 KB

Ostrzeżenie w trybie zgodności z rozmiarem strony

Ostrzeżenie w trybie zgodności z rozmiarem strony

Opcja zgodności wstecznej 16 KB jest dostępna, gdy urządzenie działa z jądrem 16 KB. Menedżer pakietów uruchamia aplikację w trybie zgodności wstecznej 16 KB, gdy spełnione są te warunki:

  • Jeśli aplikacja zawiera pliki ELF (z rozszerzeniem .so) z wyrównaniem segmentu LOAD o rozmiarze 4 KB.
  • Jeśli spakowany plik APK zawiera nieskompresowane pliki ELF, które są wyrównane do 4 KB.

Jeśli menedżer pakietów włączył dla aplikacji tryb zgodności wstecznej 16 KB, przy pierwszym uruchomieniu aplikacji pojawi się ostrzeżenie, że działa ona w trybie zgodności wstecznej 16 KB.

Tryb zgodności wstecznej z rozmiarem strony 16 KB umożliwia działanie niektórych aplikacji, ale aby zapewnić najlepszą niezawodność i stabilność, aplikacje powinny być dostosowane do rozmiaru strony 16 KB.

Na stronie z informacjami o aplikacji w sekcji Zaawansowane włącz lub wyłącz ustawienie Uruchamiaj aplikację w trybie zgodności z rozmiarem strony, aby włączyć lub wyłączyć tryb zgodności wstecznej 16 KB w przypadku konkretnej aplikacji. To ustawienie jest widoczne tylko wtedy, gdy urządzenie działa ze stronami o rozmiarze 16 KB.

Ustawienie trybu zgodności z rozmiarem strony

Ustawienie trybu zgodności z rozmiarem strony

Aby wymusić zgodność wsteczną z rozmiarem 16 KB w przypadku każdej aplikacji na urządzeniu:

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

Aby wymusić wyłączenie zgodności wstecznej z rozmiarem 16 KB w przypadku wszystkich aplikacji na urządzeniu:

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

W Androidzie 17 możesz też wymusić wyłączenie zgodności wstecznej z rozmiarem 16 KB w przypadku każdej aplikacji i spowodować natychmiastowe przerwanie działania niezgodnego pliku binarnego:

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

Ustaw właściwość android:pageSizeCompat na wartość enabled lub disabled, aby włączyć lub wyłączyć tryb zgodności wstecznej dla konkretnej aplikacji w jej pliku AndroidManifest.xml. Gdy ta właściwość jest ustawiona, aplikacja nie wyświetla ostrzeżeń o trybie zgodności wstecznej podczas uruchamiania.