Dexpreopt- und -Prüfungen

In Android 12 wurden Änderungen am Build-System für die AOT-Kompilierung von DEX-Dateien (dexpreopt) für Java-Module mit <uses-library>-Abhängigkeiten vorgenommen. In einigen Fällen können diese Änderungen am Buildsystem zu Build-Fehlern führen. Auf dieser Seite kannst du dich auf Brüche vorbereiten und den Anleitungen folgen, um sie zu beheben und zu minimieren.

Dexpreopt ist der Prozess, bei dem Java-Bibliotheken und ‑Apps vorab kompiliert werden. 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 freigegebenen Bibliotheken, die von einem Java-Modul (einer Bibliothek oder einer App) verwendet werden, wird als Klassenladekontext (CLC) bezeichnet. Damit dexpreopt korrekt funktioniert, müssen die CLCs der Build- und Laufzeit übereinstimmen. Die CLC zur Buildzeit wird vom dex2oat-Compiler zum Zeitpunkt der dexpreopt-Phase verwendet (sie wird in den ODEX-Dateien aufgezeichnet). Die CLC zur Laufzeit ist der Kontext, in dem der vorab kompilierte Code auf dem Gerät geladen wird.

Diese CLCs für Build- und Laufzeit müssen sowohl aus Gründen der Korrektheit als auch der Leistung übereinstimmen. Aus Gründen der Korrektheit müssen doppelte Klassen behandelt werden. Wenn die Abhängigkeiten der freigegebenen Bibliothek zur Laufzeit von denen abweichen, die für die Kompilierung verwendet wurden, werden einige Klassen möglicherweise anders aufgelöst, was zu subtilen Laufzeitfehlern führen kann. 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 Abweichung zwischen CLCs der Build- und Laufzeit erkennt, werden dexpreopt-Artefakte abgelehnt und stattdessen dexopt ausgeführt. Bei nachfolgenden Starts ist das in Ordnung, da die Apps im Hintergrund dexoptiert und auf dem Laufwerk gespeichert werden können.

Betroffene Bereiche von Android

Dies betrifft alle Java-Anwendungen und ‑Bibliotheken, die Laufzeitabhängigkeiten von anderen Java-Bibliotheken haben. Android hat Tausende von Apps, von denen Hunderte gemeinsame Bibliotheken verwenden. Auch Partner sind betroffen, da sie eigene Bibliotheken und Apps haben.

Unterbrechungsä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 aus Leistungsgründen keine beliebigen Dateien lesen darf, wenn es Build-Regeln generiert. Außerdem kann das Manifest in einem APK oder einem Prebuilt verpackt sein. Daher müssen die <uses-library>-Informationen in den Build-Dateien (Android.bp oder Android.mk) vorhanden sein.

Bisher wurde in ART eine Umgehung verwendet, bei der Abhängigkeiten von freigegebenen Bibliotheken (&-classpath) ignoriert wurden. Dies war unsicher und führte zu subtilen Fehlern. Daher wurde die Umgehung in Android 12 entfernt.

Java-Module, die in ihren Build-Dateien keine korrekten <uses-library>-Informationen enthalten, können daher zu Build-Fehlern (durch eine CLC-Nichtübereinstimmung bei der Buildzeit) oder Regressionen beim ersten Start (durch eine CLC-Nichtübereinstimmung bei der Bootzeit gefolgt von dexopt) führen.

Migrationspfad

So beheben Sie einen fehlerhaften Build:

  1. So deaktivieren Sie die Buildzeitprüfung für ein bestimmtes Produkt global:

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    im Produkt-Makefile. Dadurch werden Buildfehler behoben (außer in Sonderfällen, die im Abschnitt Fehler beheben aufgeführt sind). Dies ist jedoch nur eine vorübergehende Problemumgehung und kann zu einer CLC-Nichtübereinstimmung beim Starten und anschließend zu dexopt führen.

  2. Beheben Sie die Fehler in den Modulen, bevor Sie die Buildzeitprüfung global deaktiviert haben, indem Sie den Builddateien die erforderlichen <uses-library>-Informationen hinzufügen. Weitere Informationen finden Sie unter Fehler beheben. Bei den meisten Modulen müssen Sie dazu einige Zeilen in Android.bp oder Android.mk hinzufügen.

  3. Deaktivieren Sie die Buildzeitprüfung und dexpreopt für die problematischen Fälle auf Modulebene. Deaktivieren Sie dexpreopt, damit Sie keine Buildzeit und keinen Speicherplatz für Artefakte verschwenden, die beim Starten abgelehnt werden.

  4. Aktivieren Sie die Buildzeitprüfung wieder global, indem Sie die in Schritt 1 festgelegte PRODUCT_BROKEN_VERIFY_USES_LIBRARIES zurücksetzen. Der Build sollte nach dieser Änderung nicht mehr fehlschlagen (aufgrund von Schritt 2 und 3).

  5. Beheben Sie die Probleme mit den Modulen, die Sie in Schritt 3 deaktiviert haben, einzeln. Aktivieren Sie dann wieder dexpreopt und die <uses-library>-Prüfung. Melden Sie bei Bedarf Fehler.

In Android 12 werden <uses-library>-Prüfungen zur Buildzeit erzwungen.

Unterbrechungen beheben

In den folgenden Abschnitten erfahren Sie, wie Sie bestimmte Arten von Unterbrechungen beheben.

Build-Fehler: CLC-Nichtübereinstimmung

Das Build-System führt 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 Build-Regeln generieren, um das Manifest zu lesen (falls erforderlich aus einem APK extrahieren) und <uses-library>-Tags im Manifest mit den <uses-library>-Informationen in den Build-Dateien zu vergleichen. Wenn die Prüfung fehlschlägt, 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 in der Fehlermeldung vorgeschlagen, 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 Buildzeit wird weiterhin durchgeführt, aber ein Fehler bei der Prüfung bedeutet nicht, dass der Build fehlgeschlagen ist. Stattdessen führt ein Prüffehler dazu, dass das Build-System den Compilerfilter „dex2oat“ in dexpreopt auf verify herabstuft, wodurch die AOT-Kompilierung für dieses Modul vollständig deaktiviert wird.
  • Verwenden Sie die Umgebungsvariable RELAX_USES_LIBRARY_CHECK=true, um das Problem schnell und global über die Befehlszeile zu beheben. Es hat denselben Effekt wie PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, ist aber für die Verwendung in der Befehlszeile vorgesehen. Die Umgebungsvariable überschreibt die Produktvariable.
  • Um die Ursache des Fehlers zu beheben, müssen Sie das Build-System auf die <uses-library>-Tags im Manifest aufmerksam machen. Aus der Fehlermeldung geht hervor, welche Bibliotheken das Problem verursachen. Das gilt auch für die Prüfung von AndroidManifest.xml oder des Manifests in einem APK, das mit aapt dump badging $APK | grep uses-library geprüft werden kann.

Für Android.bp-Module:

  1. Suchen Sie in der libs-Eigenschaft des Moduls nach der fehlenden Bibliothek. Ist das der Fall, fügt Soong solche Bibliotheken normalerweise automatisch hinzu, mit folgenden Ausnahmen:

    • 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).

    Fügen Sie provides_uses_lib: "<library-name>" in die Bibliothekdefinition Android.bp ein, um das Problem vorübergehend zu beheben. Für eine langfristige Lösung beheben Sie das zugrunde liegende Problem: Konvertieren Sie die Bibliothek in eine SDK-Bibliothek oder ändern Sie den Namen des Moduls.

  2. Wenn das Problem durch den vorherigen Schritt nicht behoben wurde, fügen Sie der Android.bp-Definition des Moduls uses_libs: ["<library-module-name>"] für erforderliche Bibliotheken oder optional_uses_libs: ["<library-module-name>"] für optionale Bibliotheken hinzu. Für diese Properties kann eine Liste von Modulnamen verwendet werden. Die relative Reihenfolge der Bibliotheken in der Liste muss mit der Reihenfolge im Manifest übereinstimmen.

Für Android.mk-Module:

  1. Prüfen Sie, ob die Bibliothek einen anderen Bibliotheksnamen (im Manifest) als Modulnamen (im Build-System) hat. Wenn ja, beheben Sie das Problem vorübergehend, indem Sie LOCAL_PROVIDES_USES_LIBRARY := <library-name> in die Datei Android.mk der Bibliothek oder provides_uses_lib: "<library-name>" in die Datei Android.bp der Bibliothek einfügen. Beide Fälle sind möglich, da ein Android.mk-Modul von einer Android.bp-Bibliothek abhängen kann. Für eine langfristige Lösung beheben Sie das zugrunde liegende Problem: Umbenennen Sie das Bibliotheksmodul.

  2. Fügen Sie der Android.mk-Definition des Moduls LOCAL_USES_LIBRARIES := <library-module-name> für erforderliche Bibliotheken und LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> für optionale Bibliotheken hinzu. Für diese Properties kann eine Liste von Modulnamen verwendet werden. 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 findet (entweder einen Pfad zur Buildzeit auf dem Host oder einen Installationspfad auf dem Gerät), schlägt der Build in der Regel fehl. Wenn kein Pfad gefunden wird, kann das darauf hindeuten, dass die Bibliothek auf unerwartete Weise konfiguriert ist. Sie können das Problem vorübergehend beheben, indem Sie dexpreopt für das betreffende 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 nicht unterstützte Szenarien zu untersuchen.

Build-Fehler: Fehlende Bibliotheksabhängigkeit

Ein Versuch, <uses-library> X aus dem Manifest des Moduls Y der Builddatei für Y hinzuzufügen, kann aufgrund der fehlenden Abhängigkeit X zu einem Buildfehler führen.

Hier ist ein Beispiel für eine Fehlermeldung für Android.bp-Module:

"Y" depends on undefined module "X"

Hier 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 ist, wenn eine Bibliothek anders benannt ist als das entsprechende Modul im Build-System. Wenn der <uses-library>-Eintrag im Manifest beispielsweise com.android.X ist, der Name des Bibliotheksmoduls aber nur X lautet, führt dies zu einem Fehler. Um dieses Problem zu beheben, teilen Sie dem Build-System mit, dass das Modul mit dem Namen X eine <uses-library> mit dem Namen com.android.X bereitstellt.

Hier 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

Abweichende CLC-Datei zur Bootzeit

Suchen Sie beim ersten Start in logcat nach Meldungen im Zusammenhang mit einer CLC-Nichtübereinstimmung, wie unten dargestellt:

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

Die Ausgabe kann Meldungen vom folgenden Format 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, muss die Buildzeitprüfung für das Modul bestanden werden. 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 und keine Bibliothek lädt. Das Build-System kann nicht alle Fälle verarbeiten, da zum Zeitpunkt des Builds nicht mit Sicherheit bekannt ist, was die App zur Laufzeit lädt.

Kontext des Klassenladers

Die CLC ist eine baumartige Struktur, die die Klassenloader-Hierarchie beschreibt. Das Build-System verwendet CLC im engeren Sinne (es deckt nur Bibliotheken, keine APKs oder benutzerdefinierten Klassenlader ab): Es ist ein Bibliotheksbaum, der die transitive Schließung aller <uses-library>-Abhängigkeiten einer Bibliothek oder App darstellt. Die Elemente der obersten Ebene eines CLC sind die direkten <uses-library>-Abhängigkeiten, die im Manifest (Classpath) angegeben sind. Jeder Knoten eines CLC-Baums ist ein <uses-library>-Knoten, der eigene <uses-library>-Unterknoten haben kann.

Da <uses-library>-Abhängigkeiten ein gerichteter azyklischer Graph und nicht unbedingt ein Baum sind, kann CLC mehrere untergeordnete Bäume für dieselbe Bibliothek enthalten. Mit anderen Worten: CLC ist die Abhängigkeitsgrafik, die zu einem Baum „entfaltet“ wurde. Die Duplizierung erfolgt nur auf logischer Ebene. Die zugrunde liegenden Class Loader werden nicht dupliziert. Bei der Laufzeit gibt es für jede Bibliothek eine einzelne Class Loader-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 auf die erste Übereinstimmung aufgelöst wird.

On-Device-CLC (Laufzeit)

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

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

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

Mit anderen Worten: Es gibt zwei Informationsquellen, mit denen PackageManager CLC zur Laufzeit erstellen kann: <uses-library>-Tags im Manifest und Abhängigkeiten von freigegebenen Bibliotheken in XML-Konfigurationen.

On-Host-CLC (Buildzeit)

CLC ist nicht nur beim Laden einer Bibliothek oder App erforderlich, 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 ausgeführt wird, enthält es dieselben Informationen wie PackageManager (Manifeste und Abhängigkeiten von freigegebenen Bibliotheken). Dexpreopt findet jedoch on-host und in einer völlig anderen Umgebung statt und muss dieselben Informationen vom Build-System abrufen.

Daher sind die von dexpreopt verwendete CLC zur Buildzeit und die von PackageManager verwendete CLC zur Laufzeit dasselbe, werden aber auf zwei unterschiedliche Arten berechnet.

Die CLCs für die Build- und Laufzeit müssen übereinstimmen, da der von dexpreopt erstellte AOT-kompilierte Code andernfalls abgelehnt wird. Um die Gleichheit der CLCs der Build- und Laufzeit zu prüfen, zeichnet der dex2oat-Compiler die CLC der Build-Zeit in den *.odex-Dateien auf (im Feld classpath der OAT-Datei-Header). Verwenden Sie den folgenden Befehl, um den gespeicherten CLC zu finden:

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

Eine Abweichung zwischen dem CLC bei der Build- und der Laufzeit wird während des Starts in logcat gemeldet. Suchen Sie mit diesem Befehl danach:

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

Eine Abweichung wirkt sich negativ auf die Leistung aus, da die Bibliothek oder App entweder deoptimiert oder ohne Optimierungen ausgeführt werden muss. Beispielsweise muss der Code der App möglicherweise aus dem APK in den Arbeitsspeicher extrahiert werden, was sehr ressourcenintensiv ist.

Eine gemeinsam genutzte Bibliothek kann optional oder erforderlich sein. Aus Sicht von dexpreopt muss eine erforderliche Bibliothek zum Zeitpunkt des Builds vorhanden sein. Andernfalls tritt ein Buildfehler auf. Eine optionale Bibliothek kann zum Zeitpunkt des Builds vorhanden oder nicht vorhanden sein: 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 der CLC hinzugefügt. Wenn der Status der Build- und Laufzeit nicht übereinstimmt (die optionale Bibliothek ist in einem Fall vorhanden, im anderen nicht), stimmen die CLCs der Build- und Laufzeit nicht überein und der kompilierte Code wird abgelehnt.

Details zum erweiterten Buildsystem (Manifest-Fixer)

Manchmal fehlen <uses-library>-Tags im Quellmanifest einer Bibliothek oder App. Das kann beispielsweise passieren, wenn eine der übergeordneten Abhängigkeiten der Bibliothek oder App ein anderes <uses-library>-Tag verwendet und das Manifest der Bibliothek oder App nicht entsprechend aktualisiert wird.

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 von einer SDK-Bibliothek abhängt, und möglicherweise wiederum transitiv über eine andere Bibliothek abhängt.

Nicht alle <uses-library>-Tags können auf diese Weise berechnet werden. Soong sollte jedoch nach Möglichkeit automatisch Manifesteinträge hinzufügen, da dies weniger fehleranfällig ist und die Wartung vereinfacht. Wenn beispielsweise viele Apps eine statische Bibliothek verwenden, die eine neue <uses-library>-Abhängigkeit hinzufügt, müssen alle Apps aktualisiert werden, was die Wartung erschwert.