Dexpreopt- und <uses-library>-Prüfungen

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

Android 12 hat Build-Systemänderungen an der AOT-Kompilierung von DEX-Dateien (dexpreopt) für Java-Module mit <uses-library> -Abhängigkeiten vorgenommen. In einigen Fällen können diese Build-Systemänderungen Builds beschädigen. Verwenden Sie diese Seite, um sich auf Brüche vorzubereiten, und befolgen Sie die Rezepte auf dieser Seite, um sie zu beheben und zu mindern.

Dexpreopt ist der Prozess der vorzeitigen Kompilierung von Java-Bibliotheken und -Apps. Dexpreopt wird zur Buildzeit auf dem Host ausgeführt (im Gegensatz zu dexopt , das auf dem Gerät ausgeführt wird). Die Struktur der Abhängigkeiten von gemeinsam genutzten Bibliotheken, die von einem Java-Modul (einer Bibliothek oder einer App) verwendet wird, wird als Class Loader Context (CLC) bezeichnet. Um die Korrektheit von dexpreopt zu gewährleisten, müssen Buildzeit- 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 Run-Time-CLC ist der Kontext, in dem der vorkompilierte Code auf das Gerät geladen wird.

Diese Build-Time- und Run-Time-CLCs müssen sowohl aus Gründen der Korrektheit als auch der Performance übereinstimmen. Aus Gründen der Korrektheit ist es notwendig, doppelte Klassen zu behandeln. Wenn sich die Abhängigkeiten der gemeinsam genutzten Bibliotheken zur Laufzeit von denen unterscheiden, die für die Kompilierung verwendet werden, werden einige der Klassen möglicherweise anders aufgelöst, was subtile Laufzeitfehler verursacht. Die Leistung wird auch durch die Laufzeitprüfungen auf doppelte Klassen beeinflusst.

Betroffene Anwendungsfälle

Der erste Start ist der Hauptanwendungsfall, der von diesen Änderungen betroffen ist: Wenn ART eine Diskrepanz zwischen Build-Time- und Run-Time-CLCs erkennt, weist es dexpreopt-Artefakte zurück und führt stattdessen dexopt aus. Für nachfolgende Starts ist dies in Ordnung, da die Apps im Hintergrund dexoptiert und auf der Festplatte gespeichert werden können.

Betroffene Bereiche von Android

Dies betrifft alle Java-Apps und -Bibliotheken, die Laufzeitabhängigkeiten zu anderen Java-Bibliotheken haben. Android hat Tausende von Apps, und Hunderte davon verwenden gemeinsam genutzte Bibliotheken. Partner sind ebenfalls betroffen, da sie über eigene Bibliotheken und Apps verfügen.

Breaking Änderungen

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 keine beliebigen Dateien lesen darf, wenn es Build-Regeln generiert (aus Leistungsgründen). Darüber hinaus kann das Manifest in einem APK oder einem vorgefertigten Paket enthalten 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 (bekannt als &-classpath ) ignorierte. Dies war unsicher und verursachte subtile Fehler, sodass die Problemumgehung in Android 12 entfernt wurde.

Infolgedessen können Java-Module, die keine korrekten <uses-library> -Informationen in ihren Build-Dateien bereitstellen, Build- Unterbrechungen (verursacht durch eine CLC-Nichtübereinstimmung zur Build-Zeit) oder Zeitregressionen beim ersten Start (verursacht durch eine CLC zur Boot-Zeit) verursachen Mismatch gefolgt von dexopt).

Migrationspfad

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

  1. Deaktivieren Sie die Build-Time-Prüfung für ein bestimmtes Produkt global durch Einstellung

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    im Produkt-Makefile. Dadurch werden Build-Fehler 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, gefolgt von dexopt, führen.

  2. Korrigieren Sie die Module, die fehlgeschlagen sind, bevor Sie die Build-Time-Prüfung global deaktiviert haben, indem Sie die erforderlichen <uses-library> -Informationen zu ihren Build-Dateien hinzufügen (Einzelheiten finden Sie unter Fehler beheben ). Für die meisten Module erfordert dies das Hinzufügen einiger Zeilen in Android.bp oder in Android.mk .

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

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

  5. Reparieren Sie nacheinander die Module, die Sie in Schritt 3 deaktiviert haben, und aktivieren Sie dann dexpreopt und die Prüfung <uses-library> erneut. Fehler melden, falls erforderlich.

Build-time <uses-library> -Überprüfungen werden in Android 12 erzwungen.

Brüche beheben

In den folgenden Abschnitten erfahren Sie, wie Sie bestimmte Arten von Brüchen beheben können.

Build-Fehler: CLC-Nichtübereinstimmung

Das Buildsystem führt während der Buildzeit eine Kohärenzprüfung zwischen den Informationen in den Dateien „ Android.bp oder Android.mk und dem Manifest durch. Das Build-System kann das Manifest nicht lesen, aber es kann Build-Regeln generieren, um das Manifest zu lesen (ggf. durch Extrahieren aus einem APK) und <uses-library> -Tags im Manifest mit den darin enthaltenen <uses-library> -Informationen vergleichen die Build-Dateien. Schlägt die Prüfung fehl, sieht der Fehler so 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:

  • Legen Sie für eine vorübergehende produktweite Korrektur PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true im Makefile des Produkts fest. Die Kohärenzprüfung zur Erstellungszeit wird weiterhin durchgeführt, aber ein Prüfungsfehler bedeutet keinen Erstellungsfehler. Stattdessen führt ein Überprüfungsfehler dazu, dass das Build-System den dex2oat-Compilerfilter herunterstuft, um ihn in dexpreopt zu verify , wodurch die AOT-Kompilierung für dieses Modul vollständig deaktiviert wird.
  • Verwenden Sie für eine schnelle, globale Befehlszeilenkorrektur die Umgebungsvariable RELAX_USES_LIBRARY_CHECK=true . Es hat den gleichen Effekt wie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , ist aber für die Verwendung in der Befehlszeile vorgesehen. Die Umgebungsvariable überschreibt die Produktvariable.
  • Für eine Lösung zur Behebung der Fehlerursache machen Sie das Buildsystem auf die <uses-library> -Tags im Manifest aufmerksam. Eine Untersuchung der Fehlermeldung zeigt, welche Bibliotheken das Problem verursachen (ebenso wie die Untersuchung AndroidManifest.xml oder des Manifests innerhalb eines 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 ihren Modulnamen (im Build-System).

    Um dies vorübergehend zu beheben, fügen Sie provided_uses_lib provides_uses_lib: "<library-name>" in der Android.bp -Bibliotheksdefinition hinzu. Beheben Sie für eine langfristige Lösung 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 uses_libs: ["<library-module-name>"] für erforderliche Bibliotheken oder optional_uses_libs: ["<library-module-name>"] für optionale Bibliotheken zu 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 dies vorübergehend, indem LOCAL_PROVIDES_USES_LIBRARY := <library-name> in der Datei Android.mk der Bibliothek hinzufügen, oder fügen Sie provided_uses_lib provides_uses_lib: "<library-name>" in der Datei Android.bp der Bibliothek hinzu (beide Fälle sind möglich, da ein Android.mk -Modul von einer Android.bp -Bibliothek abhängen könnte). Beheben Sie für eine langfristige Lösung 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 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 einer <uses-library> DEX-JAR finden kann (entweder einen Build-Time-Pfad auf dem Host oder einen Installationspfad auf dem Gerät), schlägt der Build normalerweise fehl. Wenn ein Pfad nicht gefunden wird, kann dies darauf hindeuten, dass die Bibliothek auf unerwartete Weise konfiguriert ist. Korrigieren 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 Szenarien 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 Quelle solcher Fehler ist, wenn eine Bibliothek anders benannt ist als ihr entsprechendes Modul im Build-System. Wenn beispielsweise der Manifesteintrag <uses-library> com.android.X , der Name des Bibliotheksmoduls jedoch nur X lautet, wird ein Fehler verursacht. Um diesen Fall zu lösen, teilen Sie dem Build-System mit, dass das Modul namens X eine <uses-library> namens com.android.X .

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-Nichtübereinstimmung beim Booten

Durchsuchen Sie beim ersten Start logcat nach Meldungen im Zusammenhang mit CLC-Nichtübereinstimmungen, wie unten gezeigt:

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

Die Ausgabe kann Nachrichten 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 dies zu beheben, stellen Sie sicher, dass die Build-Time-Prüfung für das Modul bestanden wird. Wenn dies 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.

Klassenladeprogramm-Kontext

Die CLC ist eine baumartige Struktur, die die Klassenlader-Hierarchie beschreibt. Das Build-System verwendet CLC im engeren Sinne (es umfasst nur Bibliotheken, keine APKs oder Ladeprogramme für benutzerdefinierte Klassen): Es ist ein Baum von Bibliotheken, der die transitive Schließung aller <uses-library> -Abhängigkeiten einer Bibliothek oder App darstellt. Die Toplevel-Elemente einer 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 seine eigenen <uses-library> -Unterknoten haben kann.

Da <uses-library> -Abhängigkeiten ein gerichteter azyklischer Graph und nicht unbedingt ein Baum sind, kann CLC mehrere Unterbäume für dieselbe Bibliothek enthalten. Mit anderen Worten, CLC ist der zu einem Baum "entfaltete" Abhängigkeitsgraph. Die Duplizierung erfolgt nur auf logischer 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 bis zur ersten Übereinstimmung aufgelöst wird.

Auf Gerät (Laufzeit) CLC

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

Für jede verwendete Bibliothek PackageManager alle ihre <uses-library> -Abhängigkeiten (als Tags im Manifest dieser Bibliothek angegeben) ab und fügt eine verschachtelte CLC für jede Abhängigkeit hinzu. Dieser Prozess setzt sich rekursiv fort, bis alle Blattknoten des konstruierten 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 ihrer üblichen Bedeutung (wie in „shared“ vs. „static“). In Android sind gemeinsam genutzte Java-Bibliotheken diejenigen, die in XML-Konfigurationen aufgeführt sind, die 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.

On-Host (Build-Time) CLC

CLC wird nicht nur beim Laden einer Bibliothek oder einer App benötigt, sondern auch beim Kompilieren einer solchen. 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 dieselben Informationen vom Build-System abrufen.

Daher sind die von dexpreopt verwendete Build-Time-CLC und die von PackageManager verwendete Run-Time-CLC dasselbe, werden aber auf zwei verschiedene Arten berechnet.

Buildzeit- 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 die gespeicherte CLC zu finden, verwenden Sie diesen Befehl:

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

Build-Zeit- und Laufzeit-CLC-Nichtübereinstimmung wird während des Bootens in logcat gemeldet. Suchen Sie mit diesem Befehl danach:

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

Eine Diskrepanz ist schlecht für die Leistung, da sie die Bibliothek oder App dazu zwingt, entweder dexoptiert zu werden oder ohne Optimierungen ausgeführt zu werden (z. B. muss der Code der App möglicherweise aus dem APK aus dem Arbeitsspeicher extrahiert werden, was ein sehr teurer Vorgang ist).

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

Details zum erweiterten Build-System (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, da die SDK-Bibliotheken in der transitiven Abhängigkeitsschließung der Bibliothek oder App enthalten sind. Die Schließung ist erforderlich, da die Bibliothek (oder App) möglicherweise von einer statischen Bibliothek abhängt, die wiederum von einer SDK-Bibliothek abhängt, und möglicherweise wiederum transitiv über eine andere Bibliothek.

Nicht alle <uses-library> -Tags können auf diese Weise berechnet werden, aber wenn möglich, ist es vorzuziehen, Soong Manifest-Einträ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.