W Androidzie 12 wprowadzono zmiany w systemie kompilacji AOT plików DEX (dexpreopt) w przypadku modułów Javy, które mają zależności <uses-library>
. W niektórych przypadkach zmiany w systemie kompilacji mogą spowodować awarię kompilacji. Na tej stronie możesz się przygotować na przypadki awarii i zapoznać się z metodami ich rozwiązywania.
Dexpreopt to proces wstępnej kompilacji bibliotek i aplikacji Java. Narzędzie dexpreopt działa na hoście w czasie kompilacji (w przeciwieństwie do narzędzia dexopt, które działa na urządzeniu). Struktura zależności biblioteki współdzielonej używanej przez moduł Java (biblioteka lub aplikacja) jest nazywana kontekstem ładowarki klas (CLC). Aby zagwarantować poprawność dexpreopt, CLC w czasie kompilacji i w czasie wykonywania muszą być takie same. CLC w czasie kompilacji to wartość używana przez kompilator dex2oat w czasie dexpreopt (jest zapisana w plikach ODEX), a CLC w czasie wykonywania to kontekst, w którym skompilowany kod jest ładowany na urządzeniu.
Te CLC w czasie kompilacji i w czasie wykonywania muszą być zgodne ze względu na poprawność i wydajność. Ze względu na poprawność konieczne jest uwzględnienie zduplikowanych zajęć. Jeśli zależności od współdzielonej biblioteki w czasie wykonywania są inne niż te używane do kompilacji, niektóre klasy mogą być rozwiązywane inaczej, co może powodować drobne błędy w czasie wykonywania. Na wydajność wpływają też kontrole czasu wykonywania dotyczące duplikatów klas.
Przypadki użycia, których dotyczy problem
Pierwsze uruchomienie jest głównym przypadkiem użycia, na który mają wpływ te zmiany: jeśli ART wykryje niezgodność między CLC w czasie kompilacji a w czasie wykonywania, odrzuca artefakty dexpreopt i zamiast tego uruchamia dexopt. W przypadku kolejnych rozruchów nie ma to znaczenia, ponieważ aplikacje mogą być dexoptowane w tle i przechowywane na dysku.
Dotknięte obszary Androida
Ma to wpływ na wszystkie aplikacje i biblioteki Java, które mają zależności w czasie wykonywania od innych bibliotek Java. Na Androida jest tysiące aplikacji, a setki z nich korzysta z bibliotek współdzielonych. Dotyczy to również partnerów, którzy mają własne biblioteki i aplikacje.
Zmiana powodująca niezgodność
Zanim system kompilacji wygeneruje reguły kompilacji dexpreopt, musi poznać zależności <uses-library>
. Nie może jednak uzyskać bezpośredniego dostępu do pliku manifestu ani 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ść). Manifest może być również zapakowany w pliku APK lub w wersji wstępnie skompilowanej. Dlatego w plikach kompilacji (Android.bp
lub Android.mk
) muszą znajdować się informacje <uses-library>
.
Wcześniej ART stosował obejście, które ignorowało zależności bibliotek współdzielonych (znane jako &-classpath
). Było to niebezpieczne i powodowało drobne błędy, dlatego w Androidzie 12 zostało usunięte.
W rezultacie moduły Java, które nie podają prawidłowych informacji <uses-library>
w plikach kompilacji, mogą powodować błędy kompilacji (spowodowane przez niezgodność CLC w czasie kompilacji) lub regresje podczas pierwszego uruchomienia (spowodowane przez niezgodność CLC w czasie uruchamiania, po której następuje dexopt).
Ścieżka migracji
Aby naprawić uszkodzoną wersję kompilacji:
Wyłączanie globalnie sprawdzania w czasie kompilacji w przypadku konkretnego produktu za pomocą ustawienia
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
w pliku make produktu. Rozwiązuje to błędy kompilacji (z wyjątkiem szczególnych przypadków wymienionych w sekcji Rozwiązywanie problemów z błędami). Jest to jednak tymczasowe obejście problemu, które może powodować niezgodność CLC podczas uruchamiania, a następnie dexopt.
Napraw moduły, które nie działały przed wyłączeniem globalnego sprawdzania w czasie kompilacji, dodając do ich plików kompilacji niezbędne informacje
<uses-library>
(szczegóły znajdziesz w sekcji Naprawianie błędów). W przypadku większości modułów wymaga to dodania kilku wierszy w plikuAndroid.bp
lubAndroid.mk
.W problematycznych przypadkach wyłącz sprawdzanie w czasie kompilacji i dexpreoptowanie na poziomie poszczególnych modułów. Wyłącz dexpreopt, aby nie marnować 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_LIBRARIES
zdefiniowane w kroku 1. Po tej zmianie kompilacja nie powinna się zakończyć niepowodzeniem (z powodu kroków 2 i 3).Po kolei napraw moduły, które zostały wyłączone w kroku 3, a potem ponownie włącz
<uses-library>
i moduł dexpreopt. W razie potrzeby zgłaszaj błędy.
W Androidzie 12 są wymuszane kontrole <uses-library>
w czasie kompilacji.
Naprawianie uszkodzeń
W sekcjach poniżej dowiesz się, jak naprawić konkretne typy uszkodzeń.
Błąd kompilacji: niezgodność CLC
System kompilacji przeprowadza kontrolę spójności na etapie kompilacji między informacjami w plikach Android.bp
lub Android.mk
a pliku manifestu. System kompilacji nie może odczytać pliku manifestu, ale może wygenerować reguły kompilacji, aby odczytać manifest (w razie potrzeby wyodrębniając go z pliku APK), i porównać tagi <uses-library>
w pliku manifestu z informacjami <uses-library>
w plikach kompilacji. Jeśli weryfikacja się nie powiedzie, błąd będzie wyglądać tak:
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 wprowadzić tymczasową poprawkę na poziomie wszystkich produktów, ustaw opcję
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
w makefile produktu. Sprawdzanie spójności w czasie kompilacji jest nadal wykonywane, ale jego niepowodzenie nie oznacza niepowodzenia kompilacji. Zamiast tego, gdy sprawdzanie zakończy się niepowodzeniem, system kompilacji obniża poziom filtra kompilatora dex2oat doverify
w dexpreopt, co całkowicie wyłącza kompilację AOT w tym module. - Aby w szybki, globalny sposób rozwiązać problem za pomocą wiersza poleceń, użyj zmiennej środowiskowej
RELAX_USES_LIBRARY_CHECK=true
. Ma taki sam efekt jakPRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, ale jest przeznaczony do użycia w wierszu poleceń. Zmienne środowiskowe zastępują zmienne usługi. - Aby naprawić błąd u źródła, wskaż systemowi kompilacji tagi
<uses-library>
w pliku manifestu. Z komunikatu o błędzie można się dowiedzieć, które biblioteki powodują problem (podobnie jak w przypadkuAndroidManifest.xml
lub pliku 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
:
Sprawdź brakujące biblioteki w właściwości
libs
modułu. Jeśli tak, Soong zwykle dodaje takie biblioteki automatycznie, z wyjątkiem tych specjalnych przypadków:- Biblioteka nie jest biblioteką pakietu SDK (jest zdefiniowana jako
java_library
, a niejava_sdk_library
). - Biblioteka ma inną nazwę biblioteki (w pliku manifestu) niż nazwa modułu (w systemie kompilacji).
Aby tymczasowo rozwiązać ten problem, dodaj
provides_uses_lib: "<library-name>"
w definicji bibliotekiAndroid.bp
. Długoterminowym rozwiązaniem jest usunięcie podstawowego problemu: przekonwertuj bibliotekę na bibliotekę pakietu SDK lub zmień nazwę jej modułu.- Biblioteka nie jest biblioteką pakietu SDK (jest zdefiniowana jako
Jeśli poprzedni krok nie rozwiązał problemu, do definicji
Android.bp
modułu dodajuses_libs: ["<library-module-name>"]
w przypadku wymaganych bibliotek luboptional_uses_libs: ["<library-module-name>"]
w przypadku opcjonalnych bibliotek. Te właściwości mogą zawierać listę nazw modułów. Kolejność bibliotek na liście musi być taka sama jak w pliku manifestu.
W przypadku modułów Android.mk
:
Sprawdź, czy nazwa biblioteki (w pliku manifestu) różni się od nazwy modułu (w systemie kompilacji). Jeśli tak, tymczasowo rozwiąż ten problem, dodając
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
do plikuAndroid.mk
w bibliotece lub dodającprovides_uses_lib: "<library-name>"
do plikuAndroid.bp
w bibliotece (oba przypadki są możliwe, ponieważ modułAndroid.mk
może zależeć od bibliotekiAndroid.bp
). Aby rozwiązać problem na stałe, usuń podstawową przyczynę: zmień nazwę modułu biblioteki.Dodaj
LOCAL_USES_LIBRARIES := <library-module-name>
dla wymaganych bibliotek; dodajLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
dla opcjonalnych bibliotek do definicjiAndroid.mk
modułu. Te właściwości akceptują listę nazw modułów. Kolejność bibliotek na liście musi być taka sama jak w pliku manifestu.
Błąd kompilacji: nieznany ścieżka biblioteki
Jeśli system kompilacji nie może znaleźć ścieżki do pliku <uses-library>
DEX jar (ścieżki kompilacji na hoście lub ścieżki instalacji na urządzeniu), kompilacja zwykle się nie powiedzie. Niemożność znalezienia ścieżki może wskazywać, że biblioteka jest skonfigurowana w nieoczekiwanym sposób. Aby tymczasowo rozwiązać problem z kompilacją, wyłącz dexpreopt w przypadku problematycznego modułu.
Android.bp (właściwości modułu):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (zmienna modułu):
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
Zgłoś błąd, aby zbadać nieobsługiwane scenariusze.
Błąd kompilacji: brak zależności biblioteki
Próba dodania do pliku kompilacji dla Y pakietu <uses-library>
X z pliku manifestu modułu 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
Typowym ź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 <uses-library>
w pliku manifestu to com.android.X
, ale nazwa modułu biblioteki to tylko X
, powoduje to błąd. Aby rozwiązać ten problem, powiedz systemowi 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 (zmienne modułu) w pliku Android.mk:
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Niezgodność jednostki sterującej w czasie rozruchu
Podczas pierwszego rozruchu przeszukaj logcat pod kątem komunikatów związanych 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 takim formacie:
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
Jeśli pojawi się ostrzeżenie o niezgodności CLC, poszukaj polecenia dexopt dla wadliwego modułu. Aby rozwiązać ten problem, sprawdź, czy moduł przeszedł weryfikację w czasie kompilacji. Jeśli to nie zadziała, być może Twój przypadek jest szczególny i 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 momencie kompilacji nie można dokładnie wiedzieć, co aplikacja wczytuje w czasie działania.
Kontekst ładowarki klas
CLC to struktura w kształcie drzewa, która opisuje hierarchię ładowarek klas. System kompilacji używa CLC w wąskim znaczeniu (dotyczy tylko bibliotek, a nie plików APK ani ładowarek klas niestandardowych): jest to drzewo bibliotek, które reprezentuje zależność przechodnią 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 pliku manifestu (ścieżka klas). Każdy węzeł drzewa CLC to węzeł <uses-library>
, który może mieć własne węzły podrzędne <uses-library>
.
Ponieważ zależności <uses-library>
to skierowany graf acykliczny, a niekoniecznie drzewo, CLC może zawierać wiele poddrzew w ramach tej samej biblioteki. Inaczej mówiąc, CLC to wykres zależności „rozwinięty” w drzewo. Duplikaty są tylko na poziomie logicznym; rzeczywiste ładowarki klas nie są duplikowane (w czasie wykonywania dla każdej biblioteki jest pojedyncza instancja ładowarki klas).
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.
Na urządzeniu (czas wykonywania) CLC
PackageManager
(w frameworks/base
) tworzy CLC, aby załadować moduł Java na urządzeniu. Dodaje biblioteki wymienione w tagach <uses-library>
w pliku manifestu modułu jako elementy CLC najwyższego poziomu.
W przypadku każdej użytej biblioteki PackageManager
pobiera wszystkie <uses-library>
zależności (określone jako tagi w pliku manifestu tej biblioteki) i dodaje do nich zagnieżdżoną CLC. Ten proces jest powtarzany rekurencyjnie, dopóki wszystkie liście węzły skonstruowanego drzewa CLC nie będą bibliotekami bez zależności <uses-library>
.
PackageManager
zna tylko biblioteki udostępnione. Definicja udostępniania w tym przypadku różni się od zwykłego znaczenia tego słowa (w opozycji do stałego). W Androidzie biblioteki współdzielone Javy to te, które są wymienione w konfiguracjach XML i zainstalowane na urządzeniu (/system/etc/permissions/platform.xml
). Każdy wpis zawiera nazwę biblioteki współdzielonej, ścieżkę do jej pliku JAR DEX oraz listę zależności (innych bibliotek współdzielonych, których używa ta biblioteka w czasie wykonywania, i które są określone w tagach <uses-library>
w pliku manifestu).
Innymi słowy, istnieją 2 źródła informacji, które umożliwiają PackageManager
tworzenie CLC w czasie wykonywania: <uses-library>
tagi w pliku manifestu i zależności biblioteki współdzielonej 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 działa na urządzeniu, ma te same informacje co PackageManager
(pliki manifestu i zależności od bibliotek).
Narzędzie dexpreopt działa jednak na hoście i w zupełnie innym środowisku, a dodatkowo musi pobierać te same informacje z systemu kompilacji.
W związku z tym CLC w czasie kompilacji używany przez dexpreopt i CLC w czasie wykonywania używany przez PackageManager
to ta sama wartość, ale obliczana na 2 różne sposoby.
CLC w czasie kompilacji i w czasie wykonywania muszą być takie same, w przeciwnym razie kod skompilowany w trybie AOT utworzony przez dexpreopt zostanie odrzucony. Aby sprawdzić, czy CLC w czasie kompilacji i w czasie wykonywania są równe, kompilator dex2oat zapisuje CLC w czasie kompilacji w plikach *.odex
(w polu classpath
nagłówka pliku OAT). Aby znaleźć zapisany plik CLC, użyj tego polecenia:
oatdump --oat-file=<FILE> | grep '^classpath = '
Niezgodność CLC w czasie kompilacji i w czasie działania jest zgłaszana w logcat podczas uruchamiania. Znajdź je za pomocą tego polecenia:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Niezgodność ma negatywny wpływ na wydajność, ponieważ zmusza bibliotekę lub aplikację do dexoptymalizacji albo do działania bez optymalizacji (np. kod aplikacji może wymagać wyodrębnienia w pamięci z pliku APK, 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 momencie kompilacji (jej brak powoduje błąd kompilacji). Opcjonalna biblioteka może być obecna lub nieobecna w momencie kompilacji: jeśli jest obecna, jest dodawana do CLC, przekazywana do dex2oat i rejestrowana w pliku *.odex
. Jeśli opcjonalna biblioteka jest nieobecna, jest pomijana i nie jest dodawana do CLC. Jeśli stan biblioteki na etapie kompilacji i w czasie wykonywania się nie zgadza (w jednym przypadku biblioteka opcjonalna jest obecna, a w drugim nie), CLC na etapie kompilacji i w czasie wykonywania się nie zgadzają, a skompilowany kod zostaje odrzucony.
Szczegóły zaawansowanego systemu kompilacji (narzędzia do naprawy pliku manifestu)
Czasami w pliku manifestu źródłowego biblioteki lub aplikacji brakuje znaczników <uses-library>
. Może się tak zdarzyć, jeśli jedna z zależności cząstkowych biblioteki lub aplikacji zaczyna używać innego znacznika <uses-library>
, a plik manifestu biblioteki lub aplikacji nie jest aktualizowany, aby uwzględnić ten znacznik.
Soong może automatycznie obliczać niektóre brakujące tagi <uses-library>
dla danej biblioteki lub aplikacji, ponieważ biblioteki SDK w zamkniętej liście zależności pośrednich biblioteki lub aplikacji. Zamknięta lista jest potrzebna, ponieważ biblioteka (lub aplikacja) może być zależna od biblioteki statycznej, która jest zależna od biblioteki pakietu SDK, a ta z kolei może być zależna pośrednio od innej biblioteki.
Nie wszystkie tagi <uses-library>
można obliczyć w ten sposób, ale jeśli to możliwe, lepiej pozwolić Soong na automatyczne dodawanie wpisów w pliku manifestu. Dzięki temu zmniejszysz liczbę błędów i uproszczając konserwację. Jeśli na przykład wiele aplikacji używa statycznej biblioteki, która dodaje nową zależność <uses-library>
, wszystkie aplikacje muszą zostać zaktualizowane, co jest trudne do utrzymania.