Android 12 ha modifiche al sistema di compilazione per la compilazione AOT
dei file DEX (dexpreopt) per i moduli Java che hanno dipendenze <uses-library>
. In alcuni casi, queste modifiche al sistema di compilazione possono interrompere
le build. Utilizza questa pagina per prepararti a eventuali interruzioni e segui le procedure indicate in questa pagina per risolverle e mitigarle.
Dexpreopt è il processo di compilazione ahead-of-time di librerie e app Java. Dexpreopt viene eseguito sull'host al momento della compilazione (anziché dexopt, che viene eseguito sul dispositivo). La struttura delle dipendenze della libreria condivisa 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. CLC in fase di compilazione è ciò che il compilatore dex2oat utilizza in fase di dexpreopt (viene registrato nei file ODEX), mentre CLC in fase di esecuzione è il contesto in cui il codice precompilato viene caricato sul dispositivo.
Questi CLC in fase di compilazione e di esecuzione devono coincidere per motivi di correttezza e prestazioni. Per correttezza, è necessario gestire le classi duplicate. Se le dipendenze della libreria condivisa in fase di runtime sono diverse da quelle utilizzate per la compilazione, alcune classi potrebbero essere risolte in modo diverso, causando bug di runtime 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 le CLC in fase di compilazione e in fase di runtime, rifiuta gli artefatti dexpreopt ed esegue dexopt. Per gli avvii successivi, questo va bene perché le app possono essere ottimizzate in background e archiviate su disco.
Aree di Android interessate
Ciò 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 di <uses-library>
prima di generare le regole di compilazione dexpreopt. Tuttavia, non può accedere direttamente al manifest
e leggere i tag <uses-library>
perché il sistema di build non è autorizzato a leggere file arbitrari quando
genera regole di build (per motivi di prestazioni). Inoltre, il manifest potrebbe
essere incluso in un APK o in una precompilata. Pertanto, le informazioni <uses-library>
devono essere presenti nei file di build (Android.bp
o Android.mk
).
In precedenza, ART utilizzava una soluzione alternativa che ignorava le dipendenze delle librerie condivise (note
come &-classpath
). Questa soluzione era pericolosa e causava bug sottili, quindi è stata rimossa in Android 12.
Di conseguenza, i moduli Java che non forniscono informazioni <uses-library>
corrette nei file di build possono causare interruzioni della build (causate da una mancata corrispondenza CLC in fase di build) o regressioni del tempo di primo avvio (causate da una mancata corrispondenza CLC in fase di avvio seguita da dexopt).
Percorso di migrazione
Per correggere una build non riuscita:
Disattivare globalmente il controllo in fase di compilazione per un determinato prodotto impostando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
nel makefile del prodotto. Questo corregge 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 CLC all'avvio seguita da dexopt.
Correggi i moduli che non sono stati compilati prima di disattivare globalmente il controllo in fase di compilazione aggiungendo le informazioni
<uses-library>
necessarie ai file di build (vedi Correzione degli errori per i dettagli). 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, in base al modulo. Disabilita dexpreopt per non sprecare tempo di compilazione e spazio di archiviazione per gli artefatti rifiutati all'avvio.
Riattiva globalmente il controllo in fase di compilazione deselezionando
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 che hai disattivato nel passaggio 3, uno alla volta, quindi riattiva dexpreopt e il controllo
<uses-library>
. Se necessario, segnala bug.
I controlli <uses-library>
in fase di compilazione vengono applicati in Android 12.
Correggere le interruzioni
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 contenute nei file
Android.bp
o Android.mk
e nel manifest. Il sistema di build non può leggere
il manifest, ma può generare regole di build per leggerlo (estrarlo
da un APK, se necessario) e confrontare i tag <uses-library>
nel manifest
con le informazioni <uses-library>
nei file di build. Se il controllo non va a buon fine,
l'errore ha il seguente aspetto:
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 diverse soluzioni, a seconda dell'urgenza:
- Per una correzione temporanea a livello di prodotto, imposta
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
nel makefile del prodotto. Il controllo di coerenza in fase 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 build esegua 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 della 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 sulla riga di comando. La variabile di ambiente sostituisce la variabile di prodotto. - Per una soluzione che corregge la causa principale dell'errore, fai in modo che il sistema di build riconosca i tag
<uses-library>
nel manifest. Un'ispezione del messaggio di errore mostra quali librerie causano il problema (come l'ispezione diAndroidManifest.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, Soong normalmente aggiunge automaticamente queste librerie, 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 manifest) rispetto al nome del modulo (nel sistema di build).
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 sottostante: 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 fornito una soluzione, 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 manifest.
Per i moduli Android.mk
:
Verifica se la libreria ha un nome diverso (nel manifest) rispetto al nome del modulo (nel sistema di compilazione). In questo caso, risolvi temporaneamente il problema aggiungendo
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
nel fileAndroid.mk
della libreria o aggiungendoprovides_uses_lib: "<library-name>"
nel fileAndroid.bp
della libreria (entrambi i casi sono possibili poiché un moduloAndroid.mk
potrebbe dipendere da una libreriaAndroid.bp
). Per una soluzione a lungo termine, risolvi il problema sottostante: rinomina il modulo della libreria.Aggiungi
LOCAL_USES_LIBRARIES := <library-module-name>
per le librerie obbligatorie; aggiungiLOCAL_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 manifest.
Errore di compilazione: percorso della libreria sconosciuto
Se il sistema di build non riesce a trovare un percorso per un file JAR DEX (un percorso in fase di build sull'host o un percorso di installazione sul dispositivo), in genere la build non va a buon fine.<uses-library>
L'impossibilità di trovare un percorso può indicare che la libreria è configurata in
modo imprevisto. Correggi temporaneamente la build 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 gli scenari non supportati.
Errore di compilazione: dipendenza libreria mancante
Un tentativo di aggiungere <uses-library>
X dal manifest del modulo Y al file di build per Y potrebbe generare un errore di build 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 libreria è
solo X
, si verifica un errore. Per risolvere questo problema, comunica al sistema di compilazione che
il modulo denominato X
fornisce un <uses-library>
denominato com.android.X
.
Questo è un esempio per le 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 CLC all'avvio
Al primo avvio, cerca in logcat i messaggi relativi alla mancata corrispondenza 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 viene visualizzato un avviso di mancata corrispondenza CLC, cerca un comando dexopt per il modulo difettoso. Per risolvere il problema, assicurati che il controllo in fase di compilazione del modulo venga superato. Se non funziona, il tuo potrebbe essere un caso speciale non supportato dal sistema di compilazione (ad esempio un'app che carica un altro APK, non una libreria). Il sistema di build non gestisce tutti i casi, perché al momento della build è 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 dei class loader. Il sistema di build utilizza CLC in senso stretto (copre solo le librerie, non gli APK o i caricatori di classi personalizzate): è 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 manifest (il classpath). Ogni nodo di un albero CLC è un nodo
<uses-library>
che potrebbe avere i propri nodi secondari <uses-library>
.
Poiché le dipendenze di <uses-library>
sono un grafico aciclico diretto e non
necessariamente un albero, CLC può contenere più sottoalberi per la stessa libreria. In
altre parole, CLC è il grafico delle dipendenze "aperto" in una struttura ad albero. La duplicazione
avviene solo a livello logico; i class loader sottostanti effettivi non vengono
duplicati (in fase di runtime esiste una sola istanza di class loader 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 in base alla 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
recupera tutte le relative 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
conosce solo le raccolte condivise. La definizione di condiviso in
questo utilizzo differisce dal suo significato abituale (come in condiviso e statico). 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 file JAR DEX e un elenco di dipendenze
(altre librerie condivise che questa utilizza in fase di runtime e specifica nei tag
<uses-library>
nel manifest).
In altre parole, esistono due fonti di informazioni che consentono a PackageManager
di costruire CLC in fase di runtime: i tag <uses-library>
nel manifest e
le dipendenze delle librerie condivise nelle configurazioni XML.
CLC on-host (in fase di build)
CLC non è necessario solo per caricare una libreria o un'app, ma anche per
compilarne una. La compilazione può avvenire sul dispositivo (dexopt) o durante la
build (dexpreopt). Poiché dexopt viene eseguito sul dispositivo, dispone delle stesse
informazioni di PackageManager
(manifest e dipendenze della libreria condivisa).
Dexpreopt, tuttavia, viene eseguito sull'host e in un ambiente completamente diverso e deve ottenere le stesse informazioni dal sistema di compilazione.
Pertanto, la CLC in fase di compilazione utilizzata da dexpreopt e la CLC in fase di esecuzione utilizzata da
PackageManager
sono la stessa cosa, ma vengono calcolate in due modi diversi.
Le CLC di compilazione e runtime devono coincidere, altrimenti il codice compilato AOT
creato da dexpreopt viene rifiutato. Per verificare l'uguaglianza delle CLC in fase di compilazione e
in fase di esecuzione, il compilatore dex2oat registra la CLC in fase di compilazione nei file *.odex
(nel campo classpath
dell'intestazione del file OAT). Per trovare il CLC archiviato, utilizza
questo comando:
oatdump --oat-file=<FILE> | grep '^classpath = '
Durante l'avvio, in logcat viene segnalata una mancata corrispondenza CLC in fase di compilazione e di runtime. Cerca con questo comando:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
La mancata corrispondenza è dannosa per il rendimento, in quanto costringe la libreria o l'app a essere dexopted 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 richiesta deve essere presente al momento della build (la sua assenza è un errore di build). 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 libreria facoltativa è assente, viene ignorata
e non viene aggiunta al CLC. Se lo stato in fase di compilazione e in fase di esecuzione non corrisponde (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 build (correttore del file manifest)
A volte i tag <uses-library>
mancano dal file manifest di origine di una
libreria o 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
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 transitiva delle dipendenze 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 manifest, in quanto è 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.