Android 12 ha apportato modifiche al sistema di compilazione AOT dei file DEX (dexpreopt) per i moduli Java con dipendenze <uses-library>
. In alcuni casi, queste modifiche al sistema di compilazione possono interrompere le compilazioni. Utilizza questa pagina per prepararti alle interruzioni e segui le ricette riportate per risolverle e ridurle al minimo.
Dexpreopt è il processo di compilazione anticipata di app e librerie Java. Dexpreopt viene eseguito sull'host in fase di compilazione (a differenza di dexopt, che avviene sul dispositivo). La struttura delle dipendenze delle librerie condivise utilizzate da un modulo Java (una libreria o un'app) è nota come contesto del caricatore delle classi (CLC). Per garantire la correttezza di dexpreopt, i CLC di compilazione e di runtime devono coincidere. Il CLC in fase di compilazione è utilizzato dal compilatore dex2oat al momento del dexpreopt (viene registrato nei file ODEX), mentre il CLC in fase di esecuzione è il contesto in cui il codice precompilato viene caricato sul dispositivo.
Questi CLC di compilazione e di runtime devono coincidere per motivi di correttezza e prestazioni. Per la correttezza, è necessario gestire le classi duplicate. Se le dipendenze delle librerie condivise in fase di esecuzione sono diverse da quelle utilizzate per la compilazione, alcune classi potrebbero essere risolte in modo diverso, causando bug di esecuzione sottili. Il rendimento è influenzato anche dai controlli di runtime per le classi duplicate.
Casi d'uso interessati
Il primo avvio è il caso d'uso principale interessato da queste modifiche: se ART rileva una mancata corrispondenza tra i CLC a tempo di compilazione e a tempo di esecuzione, rifiuta gli elementi dexpreopt e esegue dexopt. Per gli avvii successivi non è un problema perché le app possono essere deselezionate in background e archiviate sul disco.
Aree di Android interessate
Questo vale per tutte le app e le librerie Java che hanno dipendenze di runtime da altre librerie Java. Android ha migliaia di app e centinaia di queste utilizzano librerie condivise. Anche i partner sono interessati, in quanto dispongono di librerie e app proprie.
Modifiche che provocano un errore
Il sistema di compilazione deve conoscere le dipendenze <uses-library>
prima di generare le regole di compilazione dexpreopt. Tuttavia, non può accedere direttamente al manifest
e leggere i tag <uses-library>
al suo interno, perché il sistema di compilazione non è autorizzato a leggere file arbitrari quando
genera le regole di compilazione (per motivi di prestazioni). Inoltre, il file manifest potrebbe essere impacchettato all'interno di un APK o di un prebuilt. Pertanto, le informazioni <uses-library>
devono essere presenti nei file di compilazione (Android.bp
o Android.mk
).
In precedenza, ART utilizzava una soluzione alternativa che ignorava le dipendenze dalle librerie condivise (nota come &-classpath
). Questa soluzione non era sicura e causava bug sottili, pertanto è stata rimossa in Android 12.
Di conseguenza, i moduli Java che non forniscono informazioni <uses-library>
corrette nei file di compilazione possono causare errori di compilazione (causati da una mancata corrispondenza del CLC in fase di compilazione) o regressioni al primo avvio (causate da una mancata corrispondenza del CLC in fase di avvio seguita da dexopt).
Percorso di migrazione
Per correggere una build non funzionante:
Disattivare a livello globale il controllo in fase di compilazione per un determinato prodotto impostando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
nel file make del prodotto. In questo modo vengono corretti gli errori di compilazione (ad eccezione dei casi speciali elencati nella sezione Correggere gli errori). Tuttavia, si tratta di una soluzione temporanea e può causare una mancata corrispondenza del CLC al momento dell'avvio followed by dexopt.
Correggi i moduli che non sono riusciti prima di disattivare a livello globale il controllo in fase di compilazione aggiungendo le informazioni
<uses-library>
necessarie ai relativi file di compilazione (per maggiori dettagli, consulta Correggere gli errori). Per la maggior parte dei moduli, è necessario aggiungere alcune righe inAndroid.bp
o inAndroid.mk
.Disattiva il controllo in fase di compilazione e dexpreopt per i casi problematici, su base di modulo. Disattiva dexpreopt per non sprecare tempo di compilazione e spazio di archiviazione per gli elementi che vengono rifiutati all'avvio.
Riattiva a livello globale il controllo in fase di compilazione disattivando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
impostato nel passaggio 1. La compilazione non dovrebbe non riuscire dopo questa modifica (a causa dei passaggi 2 e 3).Correggi i moduli disattivati nel passaggio 3, uno alla volta, quindi riattiva dexpreopt e il controllo
<uses-library>
. Se necessario, segnala i bug.
I controlli <uses-library>
in fase di compilazione vengono applicati in Android 12.
Correggere i problemi
Le sezioni seguenti spiegano come correggere tipi specifici di interruzioni.
Errore di compilazione: mancata corrispondenza CLC
Il sistema di compilazione esegue un controllo di coerenza in fase di compilazione tra le informazioni nei file Android.bp
o Android.mk
e il manifest. Il sistema di compilazione non può leggere il file manifest, ma può generare regole di compilazione per leggerlo (estraendolo da un APK, se necessario) e confrontare i tag <uses-library>
nel file manifest con le informazioni <uses-library>
nei file di compilazione. Se il controllo non va a buon fine,
l'errore è simile al seguente:
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
Come suggerisce il messaggio di errore, esistono più soluzioni, a seconda dell'urgenza:
- Per una correzione temporanea a livello di prodotto, imposta
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
nel file make del prodotto. Il controllo di coerenza in fase di compilazione viene comunque eseguito, ma un errore di controllo non indica un errore di compilazione. Invece, un errore di controllo fa sì che il sistema di build esegui il downgrade del filtro del compilatore dex2oat averify
in dexpreopt, il che disattiva completamente la compilazione AOT per questo modulo. - Per una correzione rapida e globale a riga di comando, utilizza la variabile di ambiente
RELAX_USES_LIBRARY_CHECK=true
. Ha lo stesso effetto diPRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, ma è destinato all'uso nella riga di comando. La variabile di ambiente sostituisce la variabile di prodotto. - Per una soluzione per la correzione della causa principale dell'errore, comunica al sistema di compilazione i tag
<uses-library>
nel file manifest. Un'ispezione del messaggio di errore mostra le librerie che causano il problema (così come l'ispezioneAndroidManifest.xml
o del manifest all'interno di un APK che può essere controllato con "aapt dump badging $APK | grep uses-library
").
Per i moduli Android.bp
:
Cerca la libreria mancante nella proprietà
libs
del modulo. Se è presente, in genere Soong aggiunge queste librerie automaticamente, tranne in questi casi speciali:- La libreria non è una libreria SDK (è definita come
java_library
anzichéjava_sdk_library
). - La libreria ha un nome diverso (nel file manifest) rispetto al nome del modulo (nel sistema di compilazione).
Per risolvere temporaneamente il problema, aggiungi
provides_uses_lib: "<library-name>"
nella definizione della libreriaAndroid.bp
. Per una soluzione a lungo termine, risolvi il problema di fondo: converti la libreria in una libreria SDK o rinomina il relativo modulo.- La libreria non è una libreria SDK (è definita come
Se il passaggio precedente non ha risolto il problema, aggiungi
uses_libs: ["<library-module-name>"]
per le librerie richieste ooptional_uses_libs: ["<library-module-name>"]
per le librerie facoltative alla definizioneAndroid.bp
del modulo. Queste proprietà accettano un elenco di nomi di moduli. L'ordine relativo delle librerie nell'elenco deve essere lo stesso dell'ordine nel file manifest.
Per i moduli Android.mk
:
Verifica se la libreria ha un nome diverso (nel file manifest) rispetto al nome del modulo (nel sistema di compilazione). In questo caso, correggi il problema temporaneamente aggiungendo
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
nel fileAndroid.mk
della raccolta oprovides_uses_lib: "<library-name>"
nel fileAndroid.bp
della raccolta (entrambi i casi sono possibili poiché un moduloAndroid.mk
potrebbe dipendere da una libreriaAndroid.bp
). Per una soluzione a lungo termine, risolvi il problema di fondo: rinomina il modulo della libreria.Aggiungi
LOCAL_USES_LIBRARIES := <library-module-name>
per le librerie obbligatorie eLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
per le librerie facoltative alla definizioneAndroid.mk
del modulo. Queste proprietà accettano un elenco di nomi di moduli. L'ordine relativo delle librerie nell'elenco deve essere lo stesso del file manifest.
Errore di compilazione: percorso della libreria sconosciuto
Se il sistema di compilazione non riesce a trovare il percorso di un file jar <uses-library>
DEX (un percorso in fase di compilazione sull'host o un percorso di installazione sul dispositivo), in genere la compilazione non va a buon fine. La mancata individuazione di un percorso può indicare che la libreria è configurata in modo imprevisto. Correggi temporaneamente la compilazione disattivando dexpreopt per il modulo problematico.
Android.bp (proprietà del modulo):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (variabili del modulo):
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
Invia una segnalazione di bug per esaminare eventuali scenari non supportati.
Errore di compilazione: dipendenza dalla libreria mancante
Un tentativo di aggiungere <uses-library>
X dal manifest del modulo Y al file di compilazione per Y potrebbe causare un errore di compilazione a causa della dipendenza mancante X.
Questo è un esempio di messaggio di errore per i moduli Android.bp:
"Y" depends on undefined module "X"
Questo è un esempio di messaggio di errore per i moduli 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
Una fonte comune di questi errori si verifica quando una libreria ha un nome diverso da quello del modulo corrispondente nel sistema di compilazione. Ad esempio, se la voce <uses-library>
del manifest è com.android.X
, ma il nome del modulo della raccolta è solo X
, si verifica un errore. Per risolvere la richiesta, indica al sistema di compilazione che il modulo denominato X
fornisce un <uses-library>
denominato com.android.X
.
Questo è un esempio di librerie Android.bp
(proprietà del modulo):
provides_uses_lib: “com.android.X”,
Questo è un esempio per le librerie Android.mk (variabile del modulo):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Mancata corrispondenza del CLC al momento dell'avvio
Al primo avvio, cerca in logcat i messaggi relativi alla mancata corrispondenza del CLC, come mostrato di seguito:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
L'output può contenere messaggi del seguente tipo:
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
Se ricevi un avviso di mancata corrispondenza CLC, cerca un comando dexopt per il modulo con problemi. Per risolvere il problema, assicurati che il controllo in fase di compilazione del modulo venga superato. Se questo non funziona, è possibile che il tuo sia un caso speciale non supportato dal sistema di compilazione (ad esempio un'app che carica un altro APK, non una libreria). Il sistema di compilazione non gestisce tutti i casi, perché in fase di compilazione è impossibile sapere con certezza cosa viene caricato dall'app in fase di esecuzione.
Contesto del caricatore di classi
Il CLC è una struttura ad albero che descrive la gerarchia del caricatore di classi. Il sistema di compilazione utilizza il CLC in senso stretto (copre solo le librerie, non gli APK o i caricatori di classi personalizzati): è un albero di librerie che rappresenta la chiusura transitiva di tutte le dipendenze <uses-library>
di una libreria o un'app. Gli elementi di primo livello di un CLC sono le dipendenze <uses-library>
dirette specificate nel manifest (il percorso di classe). Ogni nodo di un albero CLC è un
<uses-library>
nodo che potrebbe avere i propri sottonodi <uses-library>
.
Poiché le dipendenze <uses-library>
sono un grafo aciclico diretto e non necessariamente un albero, il CLC può contenere più sottoalberi per la stessa libreria. In altre parole, il CLC è il grafico delle dipendenze "aperto" in un albero. La duplicazione avviene solo a livello logico; i caricatori di classi sottostanti effettivi non vengono duplicati (in fase di runtime è presente una singola istanza del caricatore di classi per ogni libreria).
CLC definisce l'ordine di ricerca delle librerie durante la risoluzione delle classi Java utilizzate dalla libreria o dall'app. L'ordine di ricerca è importante perché le librerie possono contenere classi duplicate e la classe viene risolta nella prima corrispondenza.
CLC on device (runtime)
PackageManager
(in frameworks/base
) crea un CLC per caricare un modulo Java sul dispositivo. Aggiunge le librerie elencate nei tag <uses-library>
nel manifest del modulo come elementi CLC di primo livello.
Per ogni libreria utilizzata, PackageManager
ottiene tutte le sue dipendenze <uses-library>
(specificate come tag nel manifest della libreria) e aggiunge un CLC nidificato per ogni dipendenza. Questo processo continua in modo ricorsivo finché tutti i nodi foglia dell'albero CLC costruito non sono librerie senza dipendenze <uses-library>
.
PackageManager
è consapevole solo delle librerie condivise. La definizione di condiviso in questo contesto è diversa dal suo significato abituale (ad esempio condiviso e statico). In Android,
le librerie Java condivise sono quelle elencate nelle configurazioni XML installate
sul dispositivo (/system/etc/permissions/platform.xml
). Ogni voce contiene il nome
di una libreria condivisa, un percorso al relativo file JAR DEX e un elenco di dipendenze
(altre librerie condivise utilizzate da questa in fase di esecuzione e specificate nei
tag <uses-library>
nel file manifest).
In altre parole, esistono due fonti di informazioni che consentono a PackageManager
di creare CLC in fase di esecuzione: i tag <uses-library>
nel manifest e le dipendenze delle librerie condivise nelle configurazioni XML.
CLC on-host (in fase di compilazione)
CLC non è necessaria solo per caricare una libreria o un'app, ma anche per compilarne una. La compilazione può avvenire sul dispositivo (dexopt) o durante la compilazione (dexpreopt). Poiché dexopt viene eseguito sul dispositivo, contiene le stesse informazioni di PackageManager
(manifest e dipendenze di librerie condivise).
Dexpreopt, invece, viene eseguito on-host e in un ambiente completamente diverso e deve ottenere le stesse informazioni dal sistema di compilazione.
Pertanto, il CLC in fase di compilazione utilizzato da dexpreopt e il CLC in fase di esecuzione utilizzato da
PackageManager
sono la stessa cosa, ma calcolati in due modi diversi.
I CLC di compilazione e di runtime devono coincidere, altrimenti il codice compilato AOT creato da dexpreopt viene rifiutato. Per verificare l'uguaglianza dei CLC di compilazione e di runtime, il compilatore dex2oat registra il CLC di compilazione nei file *.odex
(nel campo classpath
dell'intestazione del file OAT). Per trovare il file CLC archiviato, utilizza questo comando:
oatdump --oat-file=<FILE> | grep '^classpath = '
La mancata corrispondenza del CLC in fase di compilazione e di esecuzione viene segnalata in logcat durante l'avvio. Cercalo con questo comando:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
La mancata corrispondenza è negativa per il rendimento, in quanto forza la libreria o l'app a essere ritirata o a funzionare senza ottimizzazioni (ad esempio, il codice dell'app potrebbe dover essere estratto in memoria dall'APK, un'operazione molto costosa).
Una libreria condivisa può essere facoltativa o obbligatoria. Dal punto di vista di dexpreopt, una libreria obbligatoria deve essere presente al momento della compilazione (la sua assenza è un errore di compilazione). Una libreria facoltativa può essere presente o assente al momento della compilazione: se presente, viene aggiunta al CLC, passata a dex2oat e registrata nel file *.odex
. Se una raccolta facoltativa non è presente, viene saltata
e non viene aggiunta al CLC. Se non c'è corrispondenza tra lo stato in fase di compilazione e quello in fase di esecuzione (la libreria facoltativa è presente in un caso, ma non nell'altro), i CLC in fase di compilazione e in fase di esecuzione non corrispondono e il codice compilato viene rifiutato.
Dettagli avanzati del sistema di compilazione (manifest fixer)
A volte i tag <uses-library>
mancano nel file manifest di origine di una biblioteca o un'app. Questo può accadere, ad esempio, se una delle dipendenze traslative della biblioteca o dell'app inizia a utilizzare un altro tag <uses-library>
e il file manifest della biblioteca o dell'app non viene aggiornato per includerlo.
Soong può calcolare automaticamente alcuni dei tag <uses-library>
mancanti per una determinata libreria o app, come le librerie SDK nella chiusura della dipendenza transitiva della libreria o dell'app. La chiusura è necessaria perché la libreria (o l'app) potrebbe dipendere da una libreria statica che dipende da una libreria SDK e, eventualmente, potrebbe dipendere di nuovo in modo transitivo da un'altra libreria.
Non tutti i tag <uses-library>
possono essere calcolati in questo modo, ma, se possibile, è preferibile lasciare che Soong aggiunga automaticamente le voci manifest. Questo approccio è meno soggetto a errori e semplifica la manutenzione. Ad esempio, quando molte app utilizzano una libreria statica che aggiunge una nuova dipendenza <uses-library>
, tutte le app devono essere aggiornate, il che è difficile da gestire.