Dexpreopt und <uses-library> Schecks

Android 12 hat Build-Systemänderungen bei der AOT-Kompilierung von DEX-Dateien (expreopt) für Java-Module mit <uses-library> -Abhängigkeiten. In einigen Fällen können diese Änderungen am Build-System dazu führen, dass Builds beschädigt werden. Nutzen Sie diese Seite, um sich auf Ausfälle vorzubereiten, und befolgen Sie die Rezepte auf dieser Seite, um diese zu beheben und zu mildern.

Dexpreopt ist der Prozess der Vorabkompilierung von Java-Bibliotheken und -Apps. Dexpreopt erfolgt auf dem Host zur Erstellungszeit (im Gegensatz zu dexopt , das auf dem Gerät erfolgt). Die Struktur gemeinsam genutzter Bibliotheksabhängigkeiten, die von einem Java-Modul (einer Bibliothek oder einer App) verwendet werden, wird als Class Loader Context (CLC) bezeichnet. Um die Korrektheit von Dexpreopt zu gewährleisten, müssen Build- und Laufzeit-CLCs übereinstimmen. Build-Time-CLC ist das, was der Dex2oat-Compiler zur Dexpreopt-Zeit verwendet (es wird in den ODEX-Dateien aufgezeichnet), und Laufzeit-CLC ist der Kontext, in dem der vorkompilierte Code auf das Gerät geladen wird.

Diese Build- und Laufzeit-CLCs müssen aus Gründen der Korrektheit und Leistung übereinstimmen. Aus Gründen der Korrektheit ist es notwendig, doppelte Klassen zu behandeln. Wenn sich die Abhängigkeiten der gemeinsam genutzten Bibliothek zur Laufzeit von denen unterscheiden, die für die Kompilierung verwendet werden, werden einige der Klassen möglicherweise anders aufgelöst, was zu subtilen Laufzeitfehlern führt. Die Leistung wird auch durch die Laufzeitprüfungen auf doppelte Klassen beeinträchtigt.

Betroffene Anwendungsfälle

Der erste Start ist der Hauptanwendungsfall, der von diesen Änderungen betroffen ist: Wenn ART eine Nichtübereinstimmung zwischen Build-Time- und Run-Time-CLCs erkennt, lehnt es Dexopt-Artefakte ab und führt stattdessen Dexopt aus. Für nachfolgende Startvorgänge ist dies in Ordnung, da die Apps im Hintergrund ausgeführt und auf der Festplatte gespeichert werden können.

Betroffene Bereiche von Android

Dies betrifft alle Java-Apps und -Bibliotheken, die Laufzeitabhängigkeiten von anderen Java-Bibliotheken haben. Android verfügt über Tausende von Apps und Hunderte davon nutzen gemeinsam genutzte Bibliotheken. Auch Partner sind betroffen, da diese über eigene Bibliotheken und Apps verfügen.

Änderungen unterbrechen

Das Build-System muss <uses-library> -Abhängigkeiten kennen, bevor es Dexpreopt-Build-Regeln generiert. Es kann jedoch nicht direkt auf das Manifest zugreifen und die darin enthaltenen <uses-library> -Tags lesen, da das Build-System beim Generieren von Build-Regeln (aus Leistungsgründen) keine beliebigen Dateien lesen darf. Darüber hinaus kann das Manifest in ein APK oder ein vorgefertigtes Paket gepackt sein. Daher müssen die <uses-library> -Informationen in den Build-Dateien ( Android.bp oder Android.mk ) vorhanden sein.

Zuvor verwendete ART eine Problemumgehung, die Abhängigkeiten von gemeinsam genutzten Bibliotheken ignorierte (bekannt als &-classpath ). Dies war unsicher und verursachte subtile Fehler, daher wurde die Problemumgehung in Android 12 entfernt.

Daher können Java-Module, die in ihren Build-Dateien keine korrekten <uses-library> -Informationen bereitstellen, Build-Abstürze (verursacht durch eine Nichtübereinstimmung des CLC zur Build-Zeit) oder Regressionen beim ersten Start (verursacht durch einen CLC zur Start-Zeit) verursachen mismatch gefolgt von dexopt).

Migrationspfad

Befolgen Sie diese Schritte, um einen fehlerhaften Build zu reparieren:

  1. Deaktivieren Sie die Erstellungszeitprüfung für ein bestimmtes Produkt global durch Einstellung

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    im Produkt-Makefile. Dadurch werden Buildfehler behoben (mit Ausnahme von Sonderfällen, die im Abschnitt „Fehler beheben“ aufgeführt sind). Dies ist jedoch eine vorübergehende Problemumgehung und kann zu einer CLC-Nichtübereinstimmung beim Booten und anschließendem Dexopt führen.

  2. Beheben Sie die Module, die fehlgeschlagen sind, bevor Sie die Build-Zeitprüfung global deaktiviert haben, indem Sie die erforderlichen <uses-library> -Informationen zu ihren Build-Dateien hinzufügen (Einzelheiten finden Sie unter Beheben von Fehlern ). Bei den meisten Modulen müssen dazu einige Zeilen in Android.bp oder Android.mk hinzugefügt werden.

  3. Deaktivieren Sie die Build-Time-Prüfung und prüfen Sie die problematischen Fälle auf Modulbasis. Deaktivieren Sie dexpreopt, damit Sie keine Build-Zeit und keinen Speicher für Artefakte verschwenden, die beim Booten abgelehnt werden.

  4. Aktivieren Sie die Build-Time-Überprüfung global erneut, indem Sie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES deaktivieren, das in Schritt 1 festgelegt wurde. Der Build sollte nach dieser Änderung nicht fehlschlagen (aufgrund der Schritte 2 und 3).

  5. Korrigieren Sie nacheinander die Module, die Sie in Schritt 3 deaktiviert haben, und aktivieren Sie dann dexpreopt und die <uses-library> -Prüfung erneut. Melden Sie ggf. Fehler.

<uses-library> -Prüfungen zur Erstellungszeit werden in Android 12 erzwungen.

Beheben Sie Brüche

In den folgenden Abschnitten erfahren Sie, wie Sie bestimmte Arten von Schäden beheben können.

Build-Fehler: CLC-Nichtübereinstimmung

Das Build-System führt zur Build-Zeit eine Kohärenzprüfung zwischen den Informationen in Android.bp oder Android.mk -Dateien und dem Manifest durch. Das Build-System kann das Manifest nicht lesen, aber es kann Build-Regeln generieren, um das Manifest zu lesen (ggf. aus einem APK extrahieren) und <uses-library> -Tags im Manifest mit den <uses-library> -Informationen in vergleichen die Build-Dateien. Schlägt die Prüfung fehl, sieht der Fehler wie folgt aus:

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

Wie die Fehlermeldung vermuten lässt, gibt es je nach Dringlichkeit mehrere Lösungen:

  • Für eine vorübergehende produktweite Korrektur legen Sie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true im Produkt-Makefile fest. Die Kohärenzprüfung zur Build-Zeit wird weiterhin durchgeführt, aber ein Prüffehler bedeutet nicht, dass ein Build fehlschlägt. Stattdessen führt ein Prüffehler dazu, dass das Build-System den Dex2oat-Compilerfilter zur verify in Dexpreopt herabstuft, wodurch die AOT-Kompilierung für dieses Modul vollständig deaktiviert wird.
  • Für eine schnelle, globale Befehlszeilenkorrektur verwenden Sie die Umgebungsvariable RELAX_USES_LIBRARY_CHECK=true . Es hat die gleiche Wirkung wie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , ist jedoch für die Verwendung in der Befehlszeile gedacht. Die Umgebungsvariable überschreibt die Produktvariable.
  • Um eine Lösung zur Behebung der Fehlerursache zu finden, machen Sie das Build-System auf die <uses-library> -Tags im Manifest aufmerksam. Eine Überprüfung der Fehlermeldung zeigt, welche Bibliotheken das Problem verursachen (ebenso wie die Überprüfung von AndroidManifest.xml oder des Manifests in einem APK, das mit „ aapt dump badging $APK | grep uses-library “ überprüft werden kann).

Für Android.bp Module:

  1. Suchen Sie in der libs Eigenschaft des Moduls nach der fehlenden Bibliothek. Wenn es vorhanden ist, fügt Soong solche Bibliotheken normalerweise automatisch hinzu, außer in diesen Sonderfällen:

    • Die Bibliothek ist keine SDK-Bibliothek (sie ist als java_library und nicht als java_sdk_library definiert).
    • Die Bibliothek hat einen anderen Bibliotheksnamen (im Manifest) als ihr Modulname (im Build-System).

    Um dies vorübergehend zu beheben, fügen Sie provides_uses_lib: "<library-name>" in der Android.bp Bibliotheksdefinition hinzu. Für eine langfristige Lösung beheben Sie das zugrunde liegende Problem: Konvertieren Sie die Bibliothek in eine SDK-Bibliothek oder benennen Sie ihr Modul um.

  2. Wenn der vorherige Schritt keine Lösung lieferte, fügen Sie uses_libs: ["<library-module-name>"] für erforderliche Bibliotheken oder optional_uses_libs: ["<library-module-name>"] für optionale Bibliotheken zum Android.bp Definition des Moduls. Diese Eigenschaften akzeptieren eine Liste von Modulnamen. Die relative Reihenfolge der Bibliotheken in der Liste muss mit der Reihenfolge im Manifest übereinstimmen.

Für Android.mk Module:

  1. Überprüfen Sie, ob die Bibliothek einen anderen Bibliotheksnamen (im Manifest) als ihren Modulnamen (im Build-System) hat. Wenn dies der Fall ist, beheben Sie das Problem vorübergehend, indem Sie LOCAL_PROVIDES_USES_LIBRARY := <library-name> in der Android.mk Datei der Bibliothek hinzufügen oder provides_uses_lib: "<library-name>" in der Android.bp Datei der Bibliothek hinzufügen (in beiden Fällen). sind möglich, da ein Android.mk Modul möglicherweise von einer Android.bp Bibliothek abhängt. Für eine langfristige Lösung beheben Sie das zugrunde liegende Problem: Benennen Sie das Bibliotheksmodul um.

  2. Fügen Sie LOCAL_USES_LIBRARIES := <library-module-name> für erforderliche Bibliotheken hinzu; Fügen Sie LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> für optionale Bibliotheken zur Android.mk Definition des Moduls hinzu. Diese Eigenschaften akzeptieren eine Liste von Modulnamen. Die relative Reihenfolge der Bibliotheken in der Liste muss mit der im Manifest übereinstimmen.

Build-Fehler: unbekannter Bibliothekspfad

Wenn das Build-System keinen Pfad zu einem <uses-library> -DEX-JAR finden kann (entweder einen Build-Zeit-Pfad auf dem Host oder einen Installationspfad auf dem Gerät), schlägt der Build normalerweise fehl. Wenn kein Pfad gefunden wird, kann dies darauf hindeuten, dass die Bibliothek auf unerwartete Weise konfiguriert ist. Reparieren Sie den Build vorübergehend, indem Sie dexpreopt für das problematische Modul deaktivieren.

Android.bp (Moduleigenschaften):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (Modulvariablen):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Melden Sie einen Fehler, um alle nicht unterstützten Szenarios zu untersuchen.

Build-Fehler: fehlende Bibliotheksabhängigkeit

Ein Versuch, <uses-library> X aus dem Manifest von Modul Y zur Build-Datei für Y hinzuzufügen, kann aufgrund der fehlenden Abhängigkeit X zu einem Build-Fehler führen.

Dies ist eine Beispielfehlermeldung für Android.bp-Module:

"Y" depends on undefined module "X"

Dies ist eine Beispielfehlermeldung für Android.mk-Module:

'.../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

Eine häufige Ursache für solche Fehler liegt darin, dass eine Bibliothek einen anderen Namen hat als ihr entsprechendes Modul im Build-System. Wenn der Manifest- <uses-library> -Eintrag beispielsweise com.android.X lautet, der Name des Bibliotheksmoduls jedoch nur X lautet, führt dies zu einem Fehler. Um diesen Fall zu lösen, teilen Sie dem Build-System mit, dass das Modul mit dem Namen X eine <uses-library> mit dem Namen com.android.X bereitstellt.

Dies ist ein Beispiel für Android.bp Bibliotheken (Moduleigenschaft):

provides_uses_lib: “com.android.X”,

Dies ist ein Beispiel für Android.mk-Bibliotheken (Modulvariable):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

CLC-Konflikt beim Booten

Durchsuchen Sie Logcat beim ersten Start nach Meldungen im Zusammenhang mit einer CLC-Nichtübereinstimmung, wie unten gezeigt:

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

Die Ausgabe kann Meldungen der hier gezeigten Form enthalten:

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Wenn Sie eine CLC-Nichtübereinstimmungswarnung erhalten, suchen Sie nach einem Dexopt-Befehl für das fehlerhafte Modul. Um das Problem zu beheben, stellen Sie sicher, dass die Buildzeitprüfung für das Modul erfolgreich ist. Wenn das nicht funktioniert, handelt es sich möglicherweise um einen Sonderfall, der vom Build-System nicht unterstützt wird (z. B. eine App, die ein anderes APK lädt, keine Bibliothek). Das Build-System verarbeitet nicht alle Fälle, da es zur Build-Zeit unmöglich ist, mit Sicherheit zu wissen, was die App zur Laufzeit lädt.

Klassenlader-Kontext

Der CLC ist eine baumartige Struktur, die die Klassenlader-Hierarchie beschreibt. Das Build-System verwendet CLC im engeren Sinne (es deckt nur Bibliotheken ab, nicht APKs oder benutzerdefinierte Klassenlader): Es handelt sich um einen Bibliotheksbaum, der den transitiven Abschluss aller <uses-library> -Abhängigkeiten einer Bibliothek oder App darstellt. Die obersten Elemente eines CLC sind die direkten <uses-library> -Abhängigkeiten, die im Manifest (dem Klassenpfad) angegeben sind. Jeder Knoten eines CLC-Baums ist ein <uses-library> -Knoten, der über eigene <uses-library> -Unterknoten verfügen kann.

Da es sich bei <uses-library> -Abhängigkeiten um einen gerichteten azyklischen Graphen und nicht unbedingt um einen Baum handelt, kann CLC mehrere Unterbäume für dieselbe Bibliothek enthalten. Mit anderen Worten, CLC ist der Abhängigkeitsgraph, der zu einem Baum „entfaltet“ wird. Die Duplizierung erfolgt nur auf einer logischen Ebene; Die eigentlichen zugrunde liegenden Klassenlader werden nicht dupliziert (zur Laufzeit gibt es für jede Bibliothek eine einzelne Klassenlader-Instanz).

CLC definiert die Suchreihenfolge von Bibliotheken beim Auflösen von Java-Klassen, die von der Bibliothek oder App verwendet werden. Die Suchreihenfolge ist wichtig, da Bibliotheken doppelte Klassen enthalten können und die Klasse nach der ersten Übereinstimmung aufgelöst wird.

Auf dem Gerät (Laufzeit) CLC

PackageManager (in frameworks/base ) erstellt einen CLC, um ein Java-Modul auf das Gerät zu laden. Es fügt die in den <uses-library> -Tags im Manifest des Moduls aufgeführten Bibliotheken als CLC-Elemente der obersten Ebene hinzu.

Für jede verwendete Bibliothek ruft PackageManager alle seine <uses-library> -Abhängigkeiten ab (angegeben als Tags im Manifest dieser Bibliothek) und fügt für jede Abhängigkeit einen verschachtelten CLC hinzu. Dieser Prozess wird rekursiv fortgesetzt, bis alle Blattknoten des erstellten CLC-Baums Bibliotheken ohne <uses-library> -Abhängigkeiten sind.

PackageManager kennt nur gemeinsam genutzte Bibliotheken. Die Definition von „shared“ in dieser Verwendung unterscheidet sich von der üblichen Bedeutung (wie „shared“ vs. „statisch“). In Android sind gemeinsam genutzte Java-Bibliotheken diejenigen, die in XML-Konfigurationen aufgeführt sind und auf dem Gerät installiert sind ( /system/etc/permissions/platform.xml ). Jeder Eintrag enthält den Namen einer gemeinsam genutzten Bibliothek, einen Pfad zu ihrer DEX-JAR-Datei und eine Liste von Abhängigkeiten (andere gemeinsam genutzte Bibliotheken, die diese zur Laufzeit verwendet und in <uses-library> -Tags in ihrem Manifest angibt).

Mit anderen Worten: Es gibt zwei Informationsquellen, die es PackageManager ermöglichen, CLC zur Laufzeit zu erstellen: <uses-library> -Tags im Manifest und Abhängigkeiten von gemeinsam genutzten Bibliotheken in XML-Konfigurationen.

CLC auf dem Host (Build-Zeit).

CLC wird nicht nur beim Laden einer Bibliothek oder App benötigt, sondern auch beim Kompilieren. Die Kompilierung kann entweder auf dem Gerät (dexopt) oder während des Builds (dexpreopt) erfolgen. Da Dexopt auf dem Gerät stattfindet, verfügt es über dieselben Informationen wie PackageManager (Manifeste und Abhängigkeiten von gemeinsam genutzten Bibliotheken). Dexpreopt findet jedoch auf dem Host und in einer völlig anderen Umgebung statt und muss die gleichen Informationen vom Build-System erhalten.

Daher sind der von dexpreopt verwendete Build-Time-CLC und der von PackageManager verwendete Laufzeit-CLC dasselbe, werden jedoch auf zwei unterschiedliche Arten berechnet.

Build- und Laufzeit-CLCs müssen übereinstimmen, andernfalls wird der von dexpreopt erstellte AOT-kompilierte Code abgelehnt. Um die Gleichheit von Build-Time- und Run-Time-CLCs zu überprüfen, zeichnet der dex2oat-Compiler Build-Time-CLCs in den *.odex Dateien (im classpath des OAT-Dateiheaders) auf. Um den gespeicherten CLC zu finden, verwenden Sie diesen Befehl:

oatdump --oat-file=<FILE> | grep '^classpath = '

Während des Startvorgangs wird in logcat eine Nichtübereinstimmung der CLCs zur Build- und Laufzeit gemeldet. Suchen Sie mit diesem Befehl danach:

logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'

Eine Nichtübereinstimmung wirkt sich negativ auf die Leistung aus, da sie dazu führt, dass die Bibliothek oder App entweder deaktiviert oder ohne Optimierungen ausgeführt wird (z. B. muss der Code der App möglicherweise aus dem APK in den Speicher extrahiert werden, ein sehr kostspieliger Vorgang).

Eine gemeinsam genutzte Bibliothek kann entweder optional oder erforderlich sein. Aus der Sicht von expreopt muss eine erforderliche Bibliothek zum Build-Zeitpunkt vorhanden sein (ihr Fehlen ist ein Build-Fehler). Eine optionale Bibliothek kann zum Zeitpunkt der Erstellung entweder vorhanden sein oder nicht: Wenn sie vorhanden ist, wird sie dem CLC hinzugefügt, an dex2oat übergeben und in der *.odex -Datei aufgezeichnet. Wenn eine optionale Bibliothek fehlt, wird sie übersprungen und nicht zum CLC hinzugefügt. Wenn es eine Diskrepanz zwischen Build-Zeit- und Laufzeitstatus gibt (die optionale Bibliothek ist in einem Fall vorhanden, im anderen jedoch nicht), stimmen die Build-Zeit- und Laufzeit-CLCs nicht überein und der kompilierte Code wird abgelehnt.

Erweiterte Details zum Buildsystem (Manifest-Fixer)

Manchmal fehlen <uses-library> -Tags im Quellmanifest einer Bibliothek oder App. Dies kann beispielsweise passieren, wenn eine der transitiven Abhängigkeiten der Bibliothek oder App beginnt, ein anderes <uses-library> -Tag zu verwenden, und das Manifest der Bibliothek oder App nicht aktualisiert wird, um es einzuschließen.

Soong kann einige der fehlenden <uses-library> -Tags für eine bestimmte Bibliothek oder App automatisch berechnen, als SDK-Bibliotheken im transitiven Abhängigkeitsabschluss der Bibliothek oder App. Die Schließung ist erforderlich, da die Bibliothek (oder App) möglicherweise von einer statischen Bibliothek abhängt, die von einer SDK-Bibliothek abhängt, und möglicherweise wiederum transitiv über eine andere Bibliothek abhängig ist.

Nicht alle <uses-library> -Tags können auf diese Weise berechnet werden, aber wenn möglich, ist es vorzuziehen, Soong Manifesteinträge automatisch hinzufügen zu lassen; es ist weniger fehleranfällig und vereinfacht die Wartung. Wenn beispielsweise viele Apps eine statische Bibliothek verwenden, die eine neue <uses-library> -Abhängigkeit hinzufügt, müssen alle Apps aktualisiert werden, was schwierig zu warten ist.