Android 12 include modifiche al sistema di compilazione per la compilazione AOT
dei file DEX (dexpreopt) per i moduli Java che hanno <uses-library>
dipendenze. In alcuni casi, queste modifiche al sistema di compilazione possono interrompere le compilazioni. Utilizza questa pagina per prepararti alle interruzioni e segui le indicazioni riportate in questa pagina per correggerle e mitigarle.
Dexpreopt è il processo di compilazione AOT delle librerie e delle app Java. Dexpreopt viene eseguito sull'host durante il tempo di compilazione (a differenza di dexopt, che viene eseguito sul dispositivo). La struttura delle dipendenze delle librerie condivise utilizzate da un modulo Java (una libreria o un'app) è nota come contesto del caricatore di classi (CLC). Per garantire la correttezza di dexpreopt, i CLC di compilazione e di runtime devono coincidere. Il CLC di compilazione è quello utilizzato dal compilatore dex2oat durante dexpreopt (viene registrato nei file ODEX), mentre il CLC di runtime è 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 runtime sono diverse da quelle utilizzate per la compilazione, alcune classi potrebbero essere risolte in modo diverso, causando bug di runtime impercettibili. Anche le prestazioni sono influenzate 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 di compilazione e di runtime, rifiuta gli artefatti dexpreopt ed esegue dexopt. Per gli avvii successivi, questo non è un problema perché le app possono essere sottoposte a dexopt in background e archiviate sul disco.
Aree di Android interessate
Questo influisce su 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 hanno le proprie librerie e app.
Modifiche che provocano un errore
Il sistema di compilazione deve conoscere le dipendenze <uses-library> prima che
generi le regole di compilazione dexpreopt. Tuttavia, non può accedere direttamente al file manifest
e leggere i tag <uses-library>
al suo interno, perché il sistema di compilazione non è autorizzato a leggere file arbitrari quando
genera regole di compilazione (per motivi di prestazioni). Inoltre, il file manifest potrebbe essere incluso in un APK o in un file precompilato. Pertanto, le <uses-library>
informazioni devono essere presenti nei file di compilazione (Android.bp o Android.mk).
In precedenza, ART utilizzava una soluzione alternativa che ignorava le dipendenze delle librerie condivise (nota
come &-classpath). Questa soluzione non era sicura e causava bug impercettibili, quindi la soluzione alternativa
è stata rimossa in Android 12.
Di conseguenza, i moduli Java che non forniscono informazioni <uses-library>
corrette nei file di compilazione possono causare interruzioni della compilazione (a causa di una
mancata corrispondenza del CLC di compilazione) o regressioni del tempo di primo avvio (a causa di una mancata corrispondenza del CLC di avvio
seguita da dexopt).
Percorso di migrazione
Segui questi passaggi per correggere una compilazione interrotta:
Disattiva a livello globale il controllo del tempo di compilazione per un determinato prodotto impostando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := truenel makefile del prodotto. In questo modo vengono corretti gli errori di compilazione (ad eccezione dei casi speciali, elencati nella sezione Correzione delle interruzioni). Tuttavia, questa è una soluzione alternativa temporanea e può causare una mancata corrispondenza del CLC di avvio seguita da dexopt.
Correggi i moduli che non sono stati compilati prima di disattivare a livello globale il controllo del tempo di compilazione aggiungendo le informazioni necessarie
<uses-library>ai file di compilazione (per i dettagli, consulta la sezione Correzione delle interruzioni). Per la maggior parte dei moduli, è necessario aggiungere alcune righe inAndroid.bpo inAndroid.mk.Disattiva il controllo del tempo di compilazione e dexpreopt per i casi problematici, in base al modulo. Disattiva dexpreopt per non sprecare tempo di compilazione e spazio di archiviazione per gli artefatti che vengono rifiutati all'avvio.
Riattiva a livello globale il controllo del tempo di compilazione annullando l'impostazione di
PRODUCT_BROKEN_VERIFY_USES_LIBRARIESimpostata 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
<uses-library>controllo. Se necessario, segnala i bug.
I controlli <uses-library> del tempo di compilazione vengono applicati in Android 12.
Correggere le interruzioni
Le sezioni seguenti descrivono come correggere tipi specifici di interruzione.
Errore di compilazione: mancata corrispondenza del CLC
Il sistema di compilazione esegue un controllo di coerenza del tempo di compilazione tra le informazioni nei file Android.bp o Android.mk e il file 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 riesce, 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 := truenel makefile del prodotto. Il controllo di coerenza del tempo di compilazione viene comunque eseguito, ma un errore di controllo non significa un errore di compilazione. Al contrario, un errore di controllo fa sì che il sistema di compilazione esegua il downgrade del filtro del compilatore dex2oat averifyin dexpreopt, il che disattiva completamente la compilazione AOT per questo modulo. - Per una correzione rapida e globale della riga di comando, utilizza la variabile di ambiente
RELAX_USES_LIBRARY_CHECK=true. Ha lo stesso effetto diPRODUCT_BROKEN_VERIFY_USES_LIBRARIES, ma è destinata all'uso nella riga di comando. La variabile di ambiente sostituisce la variabile del prodotto. - Per una soluzione che corregge la causa principale dell'errore, fai in modo che il sistema di compilazione riconosca
i tag
<uses-library>nel file manifest. Un'ispezione del messaggio di errore mostra quali librerie causano il problema (così come l'ispezione diAndroidManifest.xmlo del file 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à
libsdel modulo. Se è presente, Soong in genere aggiunge automaticamente queste librerie, tranne in questi casi speciali:- La libreria non è una libreria SDK (è definita come
java_libraryanzichéjava_sdk_library). - La libreria ha un nome di libreria diverso (nel file manifest) dal 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, correggi il problema sottostante: converti la libreria in una libreria SDK o rinomina il modulo.- La libreria non è una libreria SDK (è definita come
Se il passaggio precedente non ha fornito una soluzione, aggiungi
uses_libs: ["<library-module-name>"]per le librerie richieste, ooptional_uses_libs: ["<library-module-name>"]per le librerie facoltative a la definizioneAndroid.bpdel 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:
Controlla se la libreria ha un nome di libreria diverso (nel file manifest) dal nome del modulo (nel sistema di compilazione). In caso affermativo, correggi temporaneamente il problema aggiungendo
LOCAL_PROVIDES_USES_LIBRARY := <library-name>nel fileAndroid.mkdella libreria oppure aggiungiprovides_uses_lib: "<library-name>"nel fileAndroid.bpdella libreria (entrambi i casi sono possibili perché un moduloAndroid.mkpotrebbe dipendere da una libreriaAndroid.bp). Per una soluzione a lungo termine, correggi il problema sottostante: rinomina il modulo della libreria.Aggiungi
LOCAL_USES_LIBRARIES := <library-module-name>per le librerie richieste; aggiungiLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>per le librerie facoltative alla definizioneAndroid.mkdel 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 un percorso per un <uses-library> JAR DEX (un
percorso di compilazione sull'host o un percorso di installazione sul dispositivo), in genere la compilazione non riesce. Se non viene trovato un percorso, significa 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
Segnala un bug per esaminare eventuali scenari non supportati.
Errore di compilazione: dipendenza della libreria mancante
Un tentativo di aggiungere <uses-library> X dal file manifest del modulo Y al file di compilazione
per Y potrebbe generare un errore di compilazione a causa della dipendenza mancante, X.
Di seguito è riportato un esempio di messaggio di errore per i moduli Android.bp:
"Y" depends on undefined module "X"
Di seguito è riportato 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 è quando una libreria ha un nome diverso dal modulo corrispondente nel sistema di compilazione. Ad esempio, se la voce del file manifest
<uses-library> è com.android.X, ma il nome del modulo della libreria è
solo X, si verifica un errore. Per risolvere questo problema, indica al sistema di compilazione che
il modulo denominato X fornisce un <uses-library> denominato com.android.X.
Di seguito è riportato un esempio per le librerie Android.bp (proprietà del modulo):
provides_uses_lib: “com.android.X”,
Di seguito è riportato un esempio per le librerie Android.mk (variabile del modulo):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Mancata corrispondenza del CLC di 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 modulo mostrato qui:
[...] 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 del CLC, cerca un comando dexopt per il modulo difettoso. Per risolvere il problema, assicurati che il controllo del tempo di compilazione per il modulo venga superato. Se non funziona, potrebbe trattarsi di 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é durante la compilazione è impossibile sapere con certezza cosa carica l'app in fase di runtime.
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 di un'app. Gli elementi di primo livello
di un CLC sono le dipendenze <uses-library> dirette specificate
nel file manifest (il classpath). Ogni nodo di un albero CLC è un
<uses-library> nodo che potrebbe avere i propri <uses-library> sotto-nodi.
Poiché le dipendenze <uses-library> sono un grafo diretto aciclico 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 esiste una singola istanza del caricatore di classi per ogni libreria).
Il 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 alla prima corrispondenza.
CLC sul dispositivo (runtime)
PackageManager (in frameworks/base) crea un CLC per caricare un modulo Java sul dispositivo. Aggiunge le librerie elencate nei tag <uses-library> nel file manifest del modulo
come elementi CLC di primo livello.
Per ogni libreria utilizzata, PackageManager recupera tutte le <uses-library>
dipendenze (specifiche come tag nel file 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 <uses-library>
dipendenze.
PackageManager riconosce solo le librerie condivise. La definizione di condivisa in questo utilizzo è diversa dal suo significato abituale (come in condivisa vs. statica). In Android,
le librerie condivise Java 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 che questa utilizza in fase di runtime e specifica nei
<uses-library> tag nel file manifest).
In altre parole, esistono due fonti di informazioni che consentono a PackageManager
di costruire il CLC in fase di runtime: i tag <uses-library> nel file manifest e
le dipendenze delle librerie condivise nelle configurazioni XML.
CLC sull'host (tempo di compilazione)
Il CLC non è necessario solo per caricare una libreria o un'app, ma anche per compilarla. La compilazione può avvenire sul dispositivo (dexopt) o durante la compilazione (dexpreopt). Poiché dexopt viene eseguito sul dispositivo, ha le stesse informazioni di PackageManager (file manifest e dipendenze delle librerie condivise).
Dexpreopt, tuttavia, viene eseguito sull'host e in un ambiente completamente diverso e deve ottenere le stesse informazioni dal sistema di compilazione.
Pertanto, il CLC del tempo di compilazione utilizzato da dexpreopt e il CLC di runtime utilizzato da PackageManager sono la stessa cosa, ma vengono calcolati in due modi diversi.
I CLC del tempo di compilazione e di runtime devono coincidere, altrimenti il codice compilato AOT creato da dexpreopt viene rifiutato. Per verificare l'uguaglianza dei CLC del tempo di compilazione e di runtime, il compilatore dex2oat registra il CLC del tempo di compilazione nei file *.odex (nel campo classpath dell'intestazione del file OAT). Per trovare il CLC memorizzato, utilizza questo comando:
oatdump --oat-file=<FILE> | grep '^classpath = '
La mancata corrispondenza del CLC del tempo di compilazione e di runtime viene segnalata in logcat durante l'avvio. Cerca con questo comando:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
La mancata corrispondenza è dannosa per le prestazioni, in quanto costringe la libreria o l'app a essere sottoposta a dexopt o a essere eseguita 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 durante la compilazione (la sua assenza è un errore di compilazione). Una libreria facoltativa può essere presente o assente al tempo di compilazione: se è presente, viene aggiunta al CLC, passata a dex2oat e registrata nel file *.odex. Se una libreria facoltativa è assente, viene ignorata e non viene aggiunta al CLC. Se si verifica una mancata corrispondenza tra lo stato del tempo di compilazione e del runtime (la libreria facoltativa è presente in un caso, ma non nell'altro), i CLC del tempo di compilazione e del runtime non corrispondono e il codice compilato viene rifiutato.
Dettagli avanzati del sistema di compilazione (correzione del file manifest)
A volte i tag <uses-library> mancano dal file manifest di origine di una
libreria o di un'app. Ciò può accadere, ad esempio, se una delle dipendenze transitive
della libreria o dell'app inizia a utilizzare un altro tag <uses-library> e il
file manifest della libreria 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 delle dipendenze transitive
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, possibilmente, potrebbe
dipendere di nuovo in modo transitivo da un'altra libreria.
Non tutti i tag <uses-library> possono essere calcolati in questo modo, ma, quando possibile, è
preferibile lasciare che Soong aggiunga automaticamente le voci del file manifest; è meno
soggetto a errori e semplifica la manutenzione. Ad esempio, quando molte app utilizzano una libratica statica
che aggiunge una nuova dipendenza <uses-library>, tutte le app devono essere
aggiornate, il che è difficile da mantenere.