In Android 12 wurden 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. 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 der vorzeitigen Kompilierung von Java-Bibliotheken und -Anwendungen. Dexpreopt erfolgt während der Build-Erstellung auf dem Host – im Gegensatz zu dexopt, das auf dem Gerät stattfindet. 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 Build-Time- und Laufzeit-CLCs müssen aus Gründen der Richtigkeit und 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 beeinträchtigt.
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. Für nachfolgende Startvorgänge ist das in Ordnung, da die Apps im Hintergrund entfernt 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. Unter Android gibt es Tausende von Apps, von denen Hunderte gemeinsam genutzte Bibliotheken verwenden. Auch Partner sind davon betroffen, da sie eigene Bibliotheken und Apps haben.
Änderungen für Werbeunterbrechung
Das Build-System muss die <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 ein APK oder ein vorkonfiguriertes Manifest 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, bei der Abhängigkeiten der gemeinsam genutzten Bibliothek ignoriert wurden (bekannt als &-classpath
). Da dies unsicher war und kleinere Fehler verursachte, wurde die Behelfslösung 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
Führen Sie die folgenden Schritte aus, um einen fehlerhaften Build zu reparieren:
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 eine vorübergehende Problemumgehung und kann dazu führen, dass beim Startvorgang eine CLC-Abweichung gefolgt von „dexopt“ folgt.
Korrigieren Sie die Module, die fehlgeschlagen sind, bevor Sie die Build-Zeitprüfung global deaktiviert haben. Fügen Sie dazu die erforderlichen
<uses-library>
-Informationen in die Build-Dateien ein. Weitere Informationen finden Sie unter Fehler beheben. Bei den meisten Modulen müssen Sie dazu einige Zeilen inAndroid.bp
oderAndroid.mk
einfügen.Deaktivieren Sie die Prüfung während der Build-Erstellung und ermitteln 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.
Aktivieren Sie die Prüfung während der Build-Erstellung global wieder, indem Sie das in Schritt 1 festgelegte
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
deaktivieren. Der Build sollte nach dieser Änderung (aufgrund der Schritte 2 und 3) nicht fehlschlagen.Korrigieren Sie die Module, die Sie in Schritt 3 deaktiviert haben, einzeln und aktivieren Sie dann dexpreopt und die
<uses-library>
-Prüfung wieder. Melden Sie bei Bedarf Fehler.
In Android 12 werden <uses-library>
-Prüfungen zur Buildzeit erzwungen.
Probleme beheben
In den folgenden Abschnitten erfahren Sie, wie Sie bestimmte Arten von Fehlern 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 (es bei Bedarf aus einem APK zu 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 die Fehlermeldung andeutet, gibt es je nach Dringlichkeit mehrere Lösungen:
- Legen Sie für eine temporäre produktweite Korrektur
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. Bei einem Prüfungsfehler führt das Build-System stattdessen ein Downgrade des dex2oat-Compiler-Filters in dexpreopt aufverify
aus, 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 denselben Effekt wiePRODUCT_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 vonAndroidManifest.xml
oder des Manifests in einem APK, das mitaapt dump badging $APK | grep uses-library
geprüft werden kann.
Für Android.bp
-Module:
Suchen Sie im Attribut
libs
des Moduls nach der fehlenden Bibliothek. Ist dies der Fall, fügt Soong solche Bibliotheken normalerweise automatisch hinzu, mit Ausnahme der folgenden Sonderfälle:- Die Bibliothek ist keine SDK-Bibliothek. Sie ist als
java_library
und nicht alsjava_sdk_library
definiert. - Die Bibliothek hat einen anderen Bibliotheksnamen (im Manifest) als ihr Modulname (im Build-System).
Fügen Sie
provides_uses_lib: "<library-name>"
in die Definition der BibliothekAndroid.bp
ein, um das Problem vorübergehend zu beheben. Eine langfristige Lösung ist die Behebung des zugrunde liegenden Problems: Konvertieren Sie die Bibliothek in eine SDK-Bibliothek oder benennen Sie das Modul um.- Die Bibliothek ist keine SDK-Bibliothek. Sie ist als
Wenn im vorherigen Schritt keine Lösung angegeben wurde, fügen Sie
uses_libs: ["<library-module-name>"]
für erforderliche Bibliotheken oderoptional_uses_libs: ["<library-module-name>"]
für optionale Bibliotheken in dieAndroid.bp
-Definition des Moduls ein. Für diese Properties kann eine Liste von Modulnamen verwendet werden. Die relative Reihenfolge der Bibliotheken in der Liste muss der Reihenfolge im Manifest entsprechen.
Für Android.mk
-Module:
Prüfen Sie, ob der Bibliotheksname (im Manifest) von dem Modulnamen (im Build-System) abweicht. Sollte dies der Fall sein, kannst du das Problem vorübergehend beheben, indem du
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
in der DateiAndroid.mk
der Bibliothek oderprovides_uses_lib: "<library-name>"
in die DateiAndroid.bp
der Bibliothek hinzufügst. Beide Fälle sind möglich, da einAndroid.mk
-Modul möglicherweise von einerAndroid.bp
-Bibliothek abhängt. Für eine langfristige Lösung beheben Sie das zugrunde liegende Problem: Benennen Sie das Bibliotheksmodul um.Fügen Sie
LOCAL_USES_LIBRARIES := <library-module-name>
für erforderliche Bibliotheken undLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
für optionale Bibliotheken in dieAndroid.mk
-Definition des Moduls ein. 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 einer <uses-library>
-DEX-JAR-Datei findet (entweder einen Pfad zur Build-Zeit 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 eine 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: Bibliotheksabhängigkeit fehlt
Wenn Sie versuchen, <uses-library>
X aus dem Manifest des Moduls Y der Builddatei für Y hinzuzufügen, kann dies 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 ein Beispiel für eine Fehlermeldung 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, dass eine Bibliothek einen anderen Namen als das entsprechende Modul im Build-System hat. Wenn der Manifesteintrag <uses-library>
beispielsweise com.android.X
lautet, der Name des Bibliotheksmoduls aber nur X
lautet, wird ein Fehler verursacht. Um diesen Fall zu lösen, teilen Sie dem Build-System mit, dass das Modul 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
Abweichender CLC beim Start
Suchen Sie beim ersten Start über logcat nach Nachrichten im Zusammenhang mit CLC-Abweichungen, wie unten dargestellt:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
Die Ausgabe kann Nachrichten im 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. Sollte dies nicht funktionieren, ist Ihr Sonderfall möglicherweise ein 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 der Erstellung nicht mit Sicherheit bekannt ist, was die App zur Laufzeit lädt.
Kontext des Klassenladeprogramms
Die CLC ist eine baumartige Struktur, die die Klassenloader-Hierarchie beschreibt. Das Build-System verwendet CLC im engeren Sinne (es deckt nur Bibliotheken ab, keine APKs oder benutzerdefinierten Klassenlader): 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 das Abhängigkeitsdiagramm, das in einem Baum „aufgeklappt“ wird. Die Duplizierung erfolgt nur auf einer logischen Ebene. Die tatsächlichen zugrunde liegenden Klassenladeprogramme werden nicht dupliziert (zur Laufzeit gibt es eine einzelne Klassenladeprogramminstanz für jede Bibliothek).
CLC definiert die Suchreihenfolge von Bibliotheken beim Auflösen von Java-Klassen, die von der Bibliothek oder Anwendung verwendet werden. Die Suchreihenfolge ist wichtig, da Bibliotheken doppelte Klassen enthalten können und die Klasse in die erste Übereinstimmung aufgelöst wird.
CLC auf dem Gerät (Laufzeit)
PackageManager
(in frameworks/base
) erstellt eine 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 erhält PackageManager
alle zugehörigen <uses-library>
-Abhängigkeiten (angegeben als Tags im Manifest dieser Bibliothek) 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“). Unter 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, die es PackageManager
ermöglichen, CLC zur Laufzeit zu erstellen: <uses-library>
-Tags im Manifest und Abhängigkeiten der gemeinsam genutzten Bibliothek in XML-Konfigurationen.
CLC auf Host (Build-Zeit)
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 das Dexopt auf dem Gerät stattfindet, enthält es dieselben Informationen wie PackageManager
(Manifeste und gemeinsam genutzte Bibliothek-Abhängigkeiten).
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 verwendeten Build-Zeit-CLC und die von PackageManager
verwendete Laufzeit-CLC identisch, werden aber auf zwei verschiedene 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 die gespeicherte 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'
Abweichungen wirken sich negativ auf die Leistung aus, da sie zwingen, die Bibliothek oder App entweder zu entfernen oder ohne Optimierungen auszuführen. So kann es z. B. passieren, dass der App-Code im Arbeitsspeicher aus dem APK extrahiert werden muss, was ein sehr teurer Vorgang ist.
Eine gemeinsam genutzte Bibliothek kann optional oder erforderlich sein. Aus dexpreopt-Perspektive muss eine erforderliche Bibliothek zum Zeitpunkt der Build-Erstellung vorhanden sein. Das Fehlen ist ein Build-Fehler. 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. Bei einer Diskrepanz zwischen Build-Zeit- und Laufzeitstatus (in einem Fall ist die optionale Bibliothek vorhanden, in dem anderen aber nicht), stimmen die Build-Zeit- und Laufzeit-CLCs 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. Dies kann beispielsweise der Fall sein, wenn für eine der transitiven Abhängigkeiten der Bibliothek oder App ein anderes <uses-library>
-Tag verwendet wird 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 Anwendungen eine statische Bibliothek verwenden, die eine neue <uses-library>
-Abhängigkeit hinzufügt, müssen alle Anwendungen aktualisiert werden. Dies ist schwierig zu verwalten.