Android 12 wprowadza zmiany w systemie kompilacji dotyczące kompilacji AOT
plików DEX (dexpreopt) w przypadku modułów Java, które mają <uses-library>
zależności. W niektórych przypadkach te zmiany w systemie kompilacji mogą powodować błędy kompilacji. Na tej stronie dowiesz się, jak przygotować się na takie sytuacje, a także jak je naprawić i im zapobiegać.
Dexpreopt to proces kompilacji bibliotek i aplikacji Java z wyprzedzeniem. Dexpreopt odbywa się na hoście w czasie kompilacji (w przeciwieństwie do dexopt, który odbywa się na urządzeniu). Struktura zależności bibliotek współużytkowanych używanych przez moduł Java (bibliotekę lub aplikację) jest nazywana kontekstem wczytywania klas (CLC). Aby zagwarantować prawidłowość dexpreopt, CLC w czasie kompilacji i CLC w czasie działania muszą się pokrywać. CLC w czasie kompilacji to kontekst, którego kompilator dex2oat używa w czasie dexpreopt (jest on zapisywany w plikach ODEX), a CLC w czasie działania to kontekst, w którym wstępnie skompilowany kod jest wczytywany na urządzeniu.
Te CLC w czasie kompilacji i CLC w czasie działania muszą się pokrywać zarówno ze względu na poprawność, jak i wydajność. Aby zapewnić poprawność, konieczne jest obsługiwanie zduplikowanych klas. Jeśli zależności bibliotek współużytkowanych w czasie działania różnią się od tych używanych do kompilacji, niektóre klasy mogą być rozwiązywane inaczej, co może powodować subtelne błędy w czasie działania. Na wydajność wpływają też kontrole zduplikowanych klas w czasie działania.
Zastosowania, których to dotyczy
Głównym zastosowaniem, na które wpływają te zmiany, jest pierwsze uruchomienie urządzenia. Jeśli ART wykryje niezgodność między CLC w czasie kompilacji a CLC w czasie działania, odrzuci artefakty dexpreopt i zamiast tego uruchomi dexopt. W przypadku kolejnych uruchomień nie ma to znaczenia, ponieważ aplikacje można zoptymalizować za pomocą dexopt w tle i zapisać na dysku.
Obszary Androida, których to dotyczy
Dotyczy to wszystkich aplikacji i bibliotek Java, które mają zależności w czasie działania od innych bibliotek Java. Android ma tysiące aplikacji, a setki z nich korzystają z bibliotek współużytkowanych. Dotyczy to też partnerów, ponieważ mają oni własne biblioteki i aplikacje.
Zmiany powodujące niezgodność
System kompilacji musi znać zależności <uses-library>, zanim
wygeneruje reguły kompilacji dexpreopt. Nie może jednak bezpośrednio uzyskać dostępu do manifestu
i odczytać w nim tagów <uses-library>
, ponieważ system kompilacji nie może odczytywać dowolnych plików podczas
generowania reguł kompilacji (ze względu na wydajność). Ponadto manifest może być spakowany w pliku APK lub prebuilt. Dlatego <uses-library>
informacje muszą być obecne w plikach kompilacji (Android.bp lub Android.mk).
Wcześniej ART używał obejścia, które ignorowało zależności bibliotek współużytkowanych (znane
jako &-classpath). Było to niebezpieczne i powodowało subtelne błędy, dlatego to obejście
zostało usunięte w Androidzie 12.
W rezultacie moduły Java, które nie zawierają prawidłowych <uses-library>
informacji w swoich plikach kompilacji, mogą powodować błędy kompilacji (spowodowane niezgodnością CLC w czasie
kompilacji) lub regresje czasu pierwszego uruchomienia (spowodowane niezgodnością CLC w czasie
uruchomienia, a następnie dexopt).
Ścieżka migracji
Aby naprawić uszkodzoną kompilację:
Wyłącz globalnie sprawdzanie w czasie kompilacji w przypadku konkretnego produktu, ustawiając
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := truew pliku makefile produktu. Naprawia to błędy kompilacji (z wyjątkiem szczególnych przypadków, wymienionych w sekcji Naprawianie błędów). Jest to jednak tymczasowe obejście, które może powodować niezgodność CLC w czasie uruchomienia, a następnie dexopt.
Napraw moduły, które nie działały, zanim globalnie wyłączysz sprawdzanie w czasie kompilacji dodając niezbędne informacje
<uses-library>do ich plików kompilacji (szczegóły znajdziesz w sekcji Naprawianie błędów). W przypadku większości modułów wymaga to dodania kilku wierszy w plikuAndroid.bplubAndroid.mk.Wyłącz sprawdzanie w czasie kompilacji i dexpreopt w przypadku problematycznych przypadków na poziomie modułu. Wyłącz dexpreopt, aby nie tracić czasu kompilacji i miejsca na dane na artefakty, które są odrzucane podczas uruchamiania.
Ponownie włącz globalnie sprawdzanie w czasie kompilacji, usuwając ustawienie
PRODUCT_BROKEN_VERIFY_USES_LIBRARIESz kroku 1. Po tej zmianie kompilacja nie powinna się nie powieść (ze względu na kroki 2 i 3).Napraw moduły, które zostały wyłączone w kroku 3, jeden po drugim, a następnie ponownie włącz dexpreopt i sprawdzanie
<uses-library>. W razie potrzeby zgłoś błędy.
W Androidzie 12 wymuszane są sprawdzanie <uses-library> w czasie kompilacji.
Naprawianie błędów
W kolejnych sekcjach dowiesz się, jak naprawić konkretne typy błędów.
Błąd kompilacji: niezgodność CLC
System kompilacji sprawdza w czasie kompilacji spójność informacji w plikach Android.bp lub Android.mk oraz w manifeście. System kompilacji nie może odczytać
manifestu, ale może wygenerować reguły kompilacji, aby go odczytać (w razie potrzeby wyodrębniając
go z pliku APK), i porównać <uses-library> tagi w manifeście
z informacjami <uses-library> w plikach kompilacji. Jeśli sprawdzanie się nie powiedzie, pojawi się taki błąd:
error: mismatch in the <uses-library> tags between the build system and the manifest:
- required libraries in build system: []
vs. in the manifest: [org.apache.http.legacy]
- optional libraries in build system: []
vs. in the manifest: [com.x.y.z]
- tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
<uses-library android:name="com.x.y.z"/>
<uses-library android:name="org.apache.http.legacy"/>
note: the following options are available:
- to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
- to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
- to fix the check, make build system properties coherent with the manifest
- see build/make/Changes.md for details
Jak sugeruje komunikat o błędzie, istnieje kilka rozwiązań, w zależności od pilności:
- Aby tymczasowo naprawić problem w całym produkcie, ustaw
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := truew pliku makefile produktu. Sprawdzanie spójności w czasie kompilacji jest nadal wykonywane, ale niepowodzenie sprawdzania nie oznacza niepowodzenia kompilacji. Zamiast tego niepowodzenie sprawdzania powoduje, że system kompilacji obniża filtr kompilatora dex2oat doverifyw dexpreopt, co całkowicie wyłącza kompilację AOT w przypadku tego modułu. - Aby szybko naprawić problem globalnie w wierszu poleceń, użyj zmiennej środowiskowej
RELAX_USES_LIBRARY_CHECK=true. Ma ona taki sam efekt jakPRODUCT_BROKEN_VERIFY_USES_LIBRARIES, ale jest przeznaczona do użycia w wierszu poleceń. Zmienna środowiskowa zastępuje zmienną produktu. - Aby naprawić błąd u źródła, poinformuj system kompilacji o
tagach
<uses-library>w manifeście. Sprawdzenie komunikatu o błędzie pokazuje, które biblioteki powodują problem (podobnie jak sprawdzenieAndroidManifest.xmllub manifestu w pliku APK, który można sprawdzić za pomocą polecenia `aapt dump badging $APK | grep uses-library`).
W przypadku modułów Android.bp:
Poszukaj brakującej biblioteki we właściwości
libsmodułu. Jeśli tam jest, Soong zwykle dodaje takie biblioteki automatycznie, z wyjątkiem tych szczególnych przypadków:- Biblioteka nie jest biblioteką SDK (jest zdefiniowana jako
java_library, a niejava_sdk_library). - Biblioteka ma inną nazwę biblioteki (w manifeście) niż nazwę modułu (w systemie kompilacji).
Aby tymczasowo rozwiązać ten problem, dodaj
provides_uses_lib: "<library-name>"w definicji bibliotekiAndroid.bp. Aby znaleźć długoterminowe rozwiązanie, napraw podstawowy problem: przekonwertuj bibliotekę na bibliotekę SDK lub zmień nazwę jej modułu.- Biblioteka nie jest biblioteką SDK (jest zdefiniowana jako
Jeśli poprzedni krok nie rozwiązał problemu, dodaj
uses_libs: ["<library-module-name>"]w przypadku wymaganych bibliotek, luboptional_uses_libs: ["<library-module-name>"]w przypadku bibliotek opcjonalnych do definicji modułuAndroid.bp. Te właściwości akceptują listę nazw modułów. Kolejność bibliotek na liście musi być taka sama jak w manifeście.
W przypadku modułów Android.mk:
Sprawdź, czy biblioteka ma inną nazwę biblioteki (w manifeście) niż nazwę modułu (w systemie kompilacji). Jeśli tak, tymczasowo rozwiąż ten problem, dodając
LOCAL_PROVIDES_USES_LIBRARY := <library-name>w plikuAndroid.mkbiblioteki lub dodającprovides_uses_lib: "<library-name>"w plikuAndroid.bpbiblioteki (oba przypadki są możliwe, ponieważ modułAndroid.mkmoże zależeć od bibliotekiAndroid.bp). Aby znaleźć długoterminowe rozwiązanie, napraw podstawowy problem: zmień nazwę modułu biblioteki.Dodaj
LOCAL_USES_LIBRARIES := <library-module-name>w przypadku wymaganych bibliotek lub dodajLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>w przypadku bibliotek opcjonalnych do definicji modułuAndroid.mk. Te właściwości akceptują listę nazw modułów. Kolejność bibliotek na liście musi być taka sama jak w manifeście.
Błąd kompilacji: nieznana ścieżka biblioteki
Jeśli system kompilacji nie może znaleźć ścieżki do pliku JAR DEX <uses-library> (ścieżki w czasie kompilacji na hoście lub ścieżki instalacji na urządzeniu), zwykle powoduje to niepowodzenie kompilacji. Niepowodzenie znalezienia ścieżki może wskazywać, że biblioteka jest skonfigurowana w nieoczekiwany sposób. Tymczasowo napraw kompilację, wyłączając dexpreopt w przypadku problematycznego modułu.
Android.bp (właściwości modułu):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (zmienne modułu):
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
Zgłoś błąd, aby zbadać wszelkie nieobsługiwane scenariusze.
Błąd kompilacji: brak zależności biblioteki
Próba dodania <uses-library> X z manifestu modułu Y do pliku kompilacji dla Y może spowodować błąd kompilacji z powodu braku zależności X.
Oto przykładowy komunikat o błędzie w przypadku modułów Android.bp:
"Y" depends on undefined module "X"
Oto przykładowy komunikat o błędzie w przypadku modułów Android.mk:
'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it
Częstym źródłem takich błędów jest sytuacja, gdy biblioteka ma inną nazwę niż odpowiadający jej moduł w systemie kompilacji. Jeśli na przykład wpis w manifeście
<uses-library> to com.android.X, ale nazwa modułu biblioteki to
tylko X, powoduje to błąd. Aby rozwiązać ten problem, poinformuj system kompilacji, że
moduł o nazwie X udostępnia <uses-library> o nazwie com.android.X.
Oto przykład bibliotek Android.bp (właściwość modułu):
provides_uses_lib: “com.android.X”,
Oto przykład bibliotek Android.mk (zmienna modułu):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Niezgodność CLC w czasie uruchomienia
Podczas pierwszego uruchomienia wyszukaj w logcat komunikaty związane z niezgodnością CLC, jak pokazano poniżej:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
Dane wyjściowe mogą zawierać komunikaty w formacie pokazanym tutaj:
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
Jeśli otrzymasz ostrzeżenie o niezgodności CLC, poszukaj polecenia dexopt dla wadliwego modułu. Aby rozwiązać ten problem, upewnij się, że sprawdzanie w czasie kompilacji w przypadku modułu zakończyło się powodzeniem. Jeśli to nie zadziała, może to być szczególny przypadek, który nie jest obsługiwany przez system kompilacji (np. aplikacja, która wczytuje inny plik APK, a nie bibliotekę). System kompilacji nie obsługuje wszystkich przypadków, ponieważ w czasie kompilacji nie można z całą pewnością wiedzieć, co aplikacja wczytuje w czasie działania.
Kontekst wczytywania klas
CLC to struktura drzewiasta, która opisuje hierarchię wczytywania klas. System
kompilacji używa CLC w wąskim znaczeniu (obejmuje tylko biblioteki, a nie pliki APK ani
niestandardowe wczytywanie klas): jest to drzewo bibliotek, które reprezentuje domknięcie
przechodnie wszystkich zależności <uses-library> biblioteki lub aplikacji. Elementy najwyższego
poziomu CLC to bezpośrednie zależności <uses-library> określone
w manifeście (ścieżka klasy). Każdy węzeł drzewa CLC to węzeł
<uses-library>, który może mieć własne <uses-library> podwęzły.
Ponieważ <uses-library> zależności są skierowanym grafem acyklicznym, a nie
koniecznie drzewem, CLC może zawierać wiele poddrzew dla tej samej biblioteki. Innymi słowy, CLC to graf zależności „rozwinięty” do drzewa. Duplikacja występuje tylko na poziomie logicznym. Rzeczywiste moduły wczytujące klasy nie są duplikowane (w czasie działania dla każdej biblioteki jest tylko 1 instancja modułu wczytującego klasy).
CLC określa kolejność wyszukiwania bibliotek podczas rozwiązywania klas Java używanych przez bibliotekę lub aplikację. Kolejność wyszukiwania jest ważna, ponieważ biblioteki mogą zawierać zduplikowane klasy, a klasa jest rozwiązywana do pierwszego dopasowania.
CLC na urządzeniu (w czasie działania)
PackageManager (w frameworks/base) tworzy CLC, aby wczytać moduł Java na urządzeniu. Dodaje biblioteki wymienione w tagach <uses-library> w manifeście modułu jako elementy CLC najwyższego poziomu.
W przypadku każdej używanej biblioteki PackageManager pobiera wszystkie jej <uses-library>
zależności (określone jako tagi w manifeście tej biblioteki) i dodaje
zagnieżdżony CLC dla każdej zależności. Proces ten jest kontynuowany rekursywnie, aż wszystkie
węzły liści skonstruowanego drzewa CLC będą bibliotekami bez <uses-library>
zależności.
PackageManager zna tylko biblioteki współużytkowane. Definicja współużytkowanej w tym przypadku różni się od jej zwykłego znaczenia (jak w przypadku współużytkowanej i statycznej). W Androidzie,
biblioteki współużytkowane Java to te, które są wymienione w konfiguracjach XML zainstalowanych
na urządzeniu (/system/etc/permissions/platform.xml). Każdy wpis zawiera nazwę
biblioteki współużytkowanej, ścieżkę do jej pliku JAR DEX i listę zależności
(inne biblioteki współużytkowane, których ta biblioteka używa w czasie działania i które określa w
<uses-library> tagach w swoim manifeście).
Innymi słowy, istnieją 2 źródła informacji, które umożliwiają PackageManager
skonstruowanie CLC w czasie działania: <uses-library> tagi w manifeście i
zależności bibliotek współużytkowanych w konfiguracjach XML.
CLC na hoście (w czasie kompilacji)
CLC jest potrzebny nie tylko podczas wczytywania biblioteki lub aplikacji, ale też podczas jej kompilowania. Kompilacja może odbywać się na urządzeniu (dexopt) lub podczas kompilacji (dexpreopt). Ponieważ dexopt odbywa się na urządzeniu, ma te same informacje co PackageManager (manifesty i zależności bibliotek współużytkowanych).
Dexpreopt odbywa się jednak na hoście i w zupełnie innym środowisku, a te same informacje musi pobierać z systemu kompilacji.
Dlatego CLC w czasie kompilacji używany przez dexpreopt i CLC w czasie działania używany przez PackageManager to to samo, ale obliczane na 2 różne sposoby.
CLC w czasie kompilacji i CLC w czasie działania muszą się pokrywać, w przeciwnym razie kod skompilowany przez AOT utworzony przez dexpreopt zostanie odrzucony. Aby sprawdzić równość CLC w czasie kompilacji i CLC w czasie działania, kompilator dex2oat zapisuje CLC w czasie kompilacji w plikach *.odex (w polu classpath nagłówka pliku OAT). Aby znaleźć zapisany CLC, użyj tego polecenia:
oatdump --oat-file=<FILE> | grep '^classpath = '
Niezgodność CLC w czasie kompilacji i CLC w czasie działania jest zgłaszana w logcat podczas uruchamiania. Wyszukaj ją za pomocą tego polecenia:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Niezgodność wpływa negatywnie na wydajność, ponieważ zmusza bibliotekę lub aplikację do zoptymalizowania za pomocą dexopt albo do działania bez optymalizacji (np. kod aplikacji może wymagać wyodrębnienia z pliku APK w pamięci, co jest bardzo kosztowną operacją).
Biblioteka współużytkowana może być opcjonalna lub wymagana. Z punktu widzenia dexpreopt wymagana biblioteka musi być obecna w czasie kompilacji (jej brak jest błędem kompilacji). Biblioteka opcjonalna może być obecna lub nieobecna w czasie kompilacji. Jeśli jest obecna, jest dodawana do CLC, przekazywana do dex2oat i zapisywana w pliku *.odex. Jeśli biblioteka opcjonalna jest nieobecna, jest pomijana i nie jest dodawana do CLC. Jeśli występuje niezgodność między stanem w czasie kompilacji a stanem w czasie działania (biblioteka opcjonalna jest obecna w jednym przypadku, ale nie w drugim), CLC w czasie kompilacji i CLC w czasie działania nie są zgodne, a skompilowany kod jest odrzucany.
Szczegóły zaawansowanego systemu kompilacji (naprawianie manifestu)
Czasami brakuje tagów <uses-library> w manifeście źródłowym biblioteki lub aplikacji. Może się tak zdarzyć na przykład, jeśli jedna z zależności przechodnich biblioteki lub aplikacji
zacznie używać innego tagu <uses-library>, a manifest biblioteki lub aplikacji
nie zostanie zaktualizowany, aby go uwzględnić.
Soong może automatycznie obliczyć niektóre brakujące tagi <uses-library> w przypadku danej biblioteki
lub aplikacji, ponieważ biblioteki SDK w domknięciu przechodnim zależności
biblioteki lub aplikacji. Domknięcie jest potrzebne, ponieważ biblioteka (lub aplikacja) może
zależeć od biblioteki statycznej, która zależy od biblioteki SDK, a być może znowu zależy
przechodnio od innej biblioteki.
Nie wszystkie tagi <uses-library> można obliczyć w ten sposób, ale jeśli to możliwe, lepiej pozwolić Soongowi automatycznie dodawać wpisy do manifestu. Jest to mniej podatne na błędy i upraszcza konserwację. Jeśli na przykład wiele aplikacji używa biblioteki statycznej, która dodaje nową zależność <uses-library>, wszystkie aplikacje muszą zostać
zaktualizowane, co jest trudne do utrzymania.