Dexpreopt i sprawdzanie atrybutu <uses-library>

W Androidzie 12 wprowadzono zmiany w systemie kompilacji w kompilacji AOT plików DEX (dexpreopt) dla modułów Java z <uses-library> zależności. W niektórych przypadkach zmiany w systemie kompilacji mogą spowodować przerwanie kompilacji. Na tej stronie możesz się przygotować na przerwy w działaniu usługi i zapoznać się z metodami ich rozwiązywania i minimalizowania.

Dexpreopt to proces z wyprzedzeniem kompilowania bibliotek Java i aplikacji. Metoda Dexpreopt jest przeprowadzana na hoście w momencie kompilacji (w przeciwieństwie do metody dexopt, która odbywa się na urządzeniu). Struktura zależności bibliotek udostępnionych w Javie (biblioteka lub aplikacja) jest określany jako kontekst wczytywania klasy (CLC). Do muszą zagwarantować prawidłowość dexpreopt, czas kompilacji i czas działania CLC pokrywają się. CLC w czasie kompilacji to wartość używana przez kompilator dex2oat w czasie dexpreopt (jest zapisywana w plikach ODEX), a CLC w czasie wykonywania to kontekst, w którym skompilowany kod jest ładowany na urządzeniu.

Te interfejsy CLC zarówno w czasie kompilacji, jak i w czasie wykonywania muszą zbiegać się ze sobą z obu powodów i wydajność kampanii. Aby zapewnić poprawność, należy obsłużyć zduplikowane klasy. 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ływa też sprawdzanie w czasie działania pod kątem duplikatów. zajęcia.

Przypadki użycia, których dotyczy problem

Pierwszy rozruch to główny przypadek użycia, którego dotyczą te zmiany: jeśli ART wykrywa niezgodność między interfejsami CLC w czasie kompilacji i w czasie wykonywania, odrzuca dexpreopt i uruchamiać dexopt. W przypadku kolejnych rozruchów nie ma to znaczenia, ponieważ aplikacje mogą być dexoptowane w tle i przechowywane na dysku.

Obszary na Androidzie, których dotyczy problem

Ma to wpływ na wszystkie biblioteki i aplikacje Java, których działanie zależy od środowiska wykonawczego z innymi bibliotekami Java. Na Androidzie są tysiące aplikacji, z których setki korzystają biblioteki udostępnione. Dotyczy to też partnerów, którzy mają własne biblioteki i aplikacje.

Zmiana powodująca niezgodność

System kompilacji musi znać zależności <uses-library>, zanim zacznie działać generuje reguły kompilacji dexpreopt. Nie ma jednak bezpośredniego dostępu do pliku manifestu. i przeczytaj <uses-library> tagów, ponieważ system kompilacji nie może odczytać dowolnych plików, gdy generuje reguły kompilacji (ze względu na wydajność). Plik manifestu może też może być spakowana w pliku APK lub gotowym pliku. Dlatego parametr <uses-library> w plikach kompilacji muszą znajdować się informacje (Android.bp lub Android.mk).

Wcześniej ART stosował obejście, które ignorowało zależności zasobów wspólnych (znane jako &-classpath). Było to niebezpieczne i spowodowało drobne błędy, więc obejście problemu została usunięta na Androidzie 12.

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:

  1. Globalnie wyłącz kontrolę w czasie kompilacji konkretnej usługi przez ustawienie

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    w pliku make produktu. Poprawia to błędy kompilacji (oprócz szczególnych przypadków, wymienionych w sekcji Usuwanie usterek). Jest to jednak tymczasowe obejście problemu, które może spowodować niezgodność CLC podczas uruchamiania, a następnie dexopt.

  2. Napraw moduły, w których wystąpiły błędy, zanim globalnie wyłączysz kontrolę w czasie kompilacji. dodając niezbędne informacje <uses-library> i plikami kompilacji (szczegóły znajdziesz w artykule Usuwanie usterek). W przypadku większości modułów wymaga to dodania kilku wierszy w języku Android.bp lub w Android.mk

  3. W przypadkach problematycznych wyłącz kontrolę w czasie kompilacji i dyrektywę dexpreopt. z każdym modułem. Wyłącz dexpreopt, aby nie marnować czasu kompilacji i miejsca na dane na artefakty, które są odrzucane podczas uruchamiania.

  4. Ponownie włącz globalnie sprawdzanie w czasie kompilacji, usuwając ustawienie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES z kroku 1. Po tej zmianie kompilacja nie powinna się nie udać (z powodu kroków 2 i 3).

  5. Popraw po kolei moduły wyłączone w kroku 3, a następnie ponownie je włączaj dexpreopt oraz test <uses-library>. 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 sprawdza spójność w czasie kompilacji między informacjami w Pliki (Android.bp lub Android.mk) i plik 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 sprawdzenie 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 wskazuje komunikat o błędzie, jest wiele rozwiązań w zależności od pilność:

  • Aby uzyskać tymczasową poprawkę w całym produkcie, ustaw wartość PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true w pliku marki produktu. Sprawdzanie spójności w czasie kompilacji jest nadal wykonywane, ale jego niepowodzenie nie oznacza niepowodzenia kompilacji. Zamiast tego błąd sprawdzania powoduje, że system kompilacji jest filtr kompilatora dex2oat na verify w dexpreopt, który wyłącza kompilację AOT w całości tego modułu.
  • Aby wprowadzić szybką, globalną poprawkę w wierszu poleceń, użyj zmiennej środowiskowej RELAX_USES_LIBRARY_CHECK=true. Ma on ten sam efekt co PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, ale jest przeznaczony do użycia w wierszu poleceń. Zmienne środowiskowe zastępują zmienne produktu.
  • Aby naprawić błąd u źródła, wskaż systemowi kompilacji tagi <uses-library> w pliku manifestu. Komunikat o błędzie wskazuje, które biblioteki powodują problem (podobnie jak AndroidManifest.xml lub plik 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):

  1. Poszukaj brakującej biblioteki we właściwości libs modułu. Jeśli tak Song zwykle automatycznie dodaje takie biblioteki automatycznie, chyba że w tych wyjątkowe przypadki:

    • Ta biblioteka nie jest biblioteką pakietu SDK (jest zdefiniowana jako java_library, niż java_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 Definicja biblioteki Android.bp. Aby to zrobić długoterminowo, rozwiąż problem problem: przekonwertuj bibliotekę na bibliotekę SDK lub zmień nazwę modułu.

  2. Jeśli poprzedni krok nie rozwiązał problemu, do definicji Android.bp modułu dodaj uses_libs: ["<library-module-name>"] w przypadku wymaganych bibliotek lub optional_uses_libs: ["<library-module-name>"] w przypadku opcjonalnych bibliotek. Te miejsca zakwaterowania akceptują listę nazwy modułów. Względna kolejność bibliotek na liście musi być taka sama zgodnie z kolejnością w pliku manifestu.

W przypadku modułów (Android.mk):

  1. Sprawdź, czy biblioteka ma inną nazwę (w pliku manifestu) niż jej nazwa nazwy modułu (w systemie kompilacji). Jeśli tak, rozwiąż ten problem tymczasowo, dodając LOCAL_PROVIDES_USES_LIBRARY := <library-name> w pliku Android.mk w bibliotece lub dodaj provides_uses_lib: "<library-name>" w Android.bp biblioteki (można to zrobić w obu przypadkach, ponieważ moduł Android.mk może zależeć od biblioteki Android.bp). Aby rozwiązać problem na stałe, napraw problem podstawowy: zmień nazwę modułu biblioteki.

  2. Dodaj LOCAL_USES_LIBRARIES := <library-module-name> dla wymaganych bibliotek; dodaj LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> dla opcjonalnych bibliotek do definicji Android.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. Jeśli nie udało się znaleźć ścieżki, może to oznaczać, że biblioteka jest skonfigurowana w w nieoczekiwany sposób. Tymczasowo napraw kompilację, wyłączając dexpreopt dla 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ć 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 dotyczący 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 inna nazwa biblioteki niż jej nazwa odpowiadający modułowi jest nazwany w systemie kompilacji. Jeśli na przykład plik manifestu Pozycja <uses-library> to com.android.X, ale nazwa modułu biblioteki to X powoduje błąd. Aby rozwiązać ten problem, powiedz systemowi kompilacji, że moduł o nazwie X zawiera <uses-library> o nazwie com.android.X.

Oto przykład bibliotek Android.bp (właściwości modułu):

provides_uses_lib: “com.android.X”,

Oto przykład bibliotek Android.mk (zmienne modułu):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Niezgodność CLC w czasie uruchamiania

Przy pierwszym uruchomieniu wyszukaj w narzędziu 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 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 z pewnością stwierdzić, co aplikacja wczytuje w czasie działania.

Kontekst ładowarki klas

CLC to struktura w kształcie drzewa, która opisuje hierarchię modułu ładowania klas. system kompilacji używa CLC w wąskim sensie (obejmuje tylko biblioteki, nie pliki APK ładowarki klasy niestandardowej): jest to drzewo bibliotek, które reprezentuje zamknięcie wszystkich zależności <uses-library> biblioteki lub aplikacji. Organizacja najwyższego poziomu elementami CLC są określone bezpośrednie zależności <uses-library> w pliku manifestu (ścieżka klasy). 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. W Innymi słowy, CLC jest „rozwiniętym” grafem zależności. do drzewa. Podwójne ładowanie występuje tylko na poziomie logicznym. Rzeczywiste ładowarki klas nie są dublowane (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.

CLC na urządzeniu (w czasie działania)

PackageManager (w frameworks/base) tworzy interfejs CLC do wczytywania modułu 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 ma informacje o tylko bibliotekach udostępnionych. 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 ona 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ą PackageManagertworzenie CLC w czasie wykonywania: <uses-library>tagi w pliku manifestu i zależności bibliotek współdzielonych 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). Dexopt odbywa się na urządzeniu, więc ma taki sam informacje jako PackageManager (pliki manifestu i zależności bibliotek udostępnionych). Metoda Dexpreopt odbywa się jednak na hoście i w zupełnie innym interfejsie i 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 kompilowany w trybie AOT utworzony przez dexpreopt zostanie odrzucony. Aby sprawdzić równość czasu kompilacji kompilatora dex2oat służącego do uruchamiania aplikacji CLC w czasie kompilacji w plikach *.odex (w polu classpath nagłówka 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. Szukaj , używając 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 biblioteki wspólnej. Jeśli stan biblioteki na etapie kompilacji i w czasie wykonywania się nie zgadzają (w jednym przypadku biblioteka opcjonalna jest obecna, a w drugim nie), CLC na etapie kompilacji i w czasie wykonywania się nie zgadzają i skompilowany kod zostaje odrzucony.

Zaawansowane szczegóły systemu kompilacji (poprawiarka manifestu)

Czasami w źródłowym pliku manifestu brakuje tagów <uses-library> z biblioteki lub aplikacji. Może się tak na przykład zdarzyć, gdy jedna z zależności pośrednich biblioteki lub aplikacji zacznie używać innego tagu <uses-library>, a tag biblioteka lub plik manifestu aplikacji nie jest aktualizowany.

Soong może obliczyć niektóre brakujące tagi <uses-library> w danej bibliotece lub aplikacja automatycznie, jako biblioteki pakietu SDK w przypadku zamknięcia zależności pośredniej w bibliotece lub aplikacji. Jest to konieczne, ponieważ biblioteka (lub aplikacja) może bazują na bibliotece statycznej, która jest zależna od biblioteki SDK, i może i przechodnie przez inną bibliotekę.

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.