Controlli di Dexpreopt e <uses-library>

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 delle librerie Java app. Dexpreopt viene eseguito in fase di compilazione sull'host (a differenza di dexopt, che avviene sul dispositivo). La struttura delle dipendenze della libreria condivisa utilizzata da un (una libreria o un'app) è noto come contesto del caricatore di classi (CLC). A garantire la correttezza delle CLC di dexpreopt, build-time e runtime devono coincidere. CLC in fase di build è ciò che il compilatore dex2oat utilizza al momento del dexpreopt (viene registrato nei file ODEX), mentre l'CLC in fase di runtime è il contesto in cui sul dispositivo viene caricato il codice precompilato.

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 runtime sottili. Le prestazioni sono influenzate anche dai controlli di runtime per la presenza di duplicati .

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 build e quelle in fase di esecuzione, rifiuta le impostazioni artefatti ed esegue dexopt. Per gli stivali successivi va bene, perché le app possono essere dexoptate in background e archiviate su disco.

Aree di Android interessate

Questo interessa tutte le app e le librerie Java che hanno dipendenze di runtime altre librerie Java. Android ha migliaia di app e centinaia di app la usano raccolte condivise. Anche i partner sono interessati, in quanto hanno i propri librerie e app.

Modifiche che provocano un errore

Il sistema di build deve conoscere le dipendenze <uses-library> prima di genera regole di build expreopt. 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 essere pacchettizzati all'interno di un APK o di un 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 impercettibili, 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:

  1. Disattivare a livello globale il controllo in fase di compilazione per un determinato prodotto impostando

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    nel makefile del prodotto. Questa operazione corregge gli errori di generazione (tranne casi speciali, indicato nella sezione Correggere i malfunzionamenti). Tuttavia, si tratta di una soluzione alternativa temporanea e può causare una mancata corrispondenza delle commissioni di categoria al momento dell'avvio seguito da dexopt.

  2. 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 la sezione Correggere gli errori). Per la maggior parte dei moduli è necessario aggiungere alcune righe in Android.bp oppure in Android.mk.

  3. Disabilita il controllo in fase di creazione e l'analisi per le richieste problematiche, dei moduli. Disattiva dexpreopt per non perdere tempo nella creazione su artefatti che vengono rifiutati al momento dell'avvio.

  4. Riattiva a livello globale il controllo del tempo di build annullando l'impostazione PRODUCT_BROKEN_VERIFY_USES_LIBRARIES impostato nel passaggio 1; la build non dovrebbero subire errori dopo questa modifica (per i passaggi 2 e 3).

  5. Correggi uno alla volta i moduli che hai disattivato nel passaggio 3, quindi riattivali. 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 risolvere tipi specifici di malfunzionamenti.

Errore di generazione: mancata corrispondenza delle commissioni a livello di categoria

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 viene superato, 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 della coerenza in fase di compilazione viene comunque eseguito, ma un errore di controllo non indica necessariamente un errore di compilazione. Un errore di controllo porta invece il sistema di compilazione a eseguire il downgrade Filtro del compilatore dex2oat su verify in dexpreopt, che disabilita la compilazione AOT per questo modulo.
  • Per una risoluzione rapida della riga di comando globale, utilizza la variabile di ambiente RELAX_USES_LIBRARY_CHECK=true. Ha lo stesso effetto di PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, ma è destinato all'uso nella riga di comando. La variabile di ambiente sostituisce la variabile di prodotto.
  • Per una soluzione alla correzione della causa principale dell'errore, rendi il sistema di build a conoscenza del i tag <uses-library> nel file manifest. Un'ispezione del messaggio di errore mostra le librerie che causano il problema (così come l'ispezione AndroidManifest.xml o il file manifest all'interno di un APK che può essere controllato con "aapt dump badging $APK | grep uses-library").

Per Android.bp moduli:

  1. 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 (nel manifest) diverso da quello del modulo (nel sistema di compilazione).

    Per risolvere il problema temporaneamente, aggiungi provides_uses_lib: "<library-name>" nel Definizione della libreria Android.bp. Per una soluzione a lungo termine, correggi il problema di base issue: converti la libreria in una libreria SDK o rinomina il relativo modulo.

  2. Se il passaggio precedente non fornisce una risoluzione, aggiungi uses_libs: ["<library-module-name>"] per le librerie richieste, o optional_uses_libs: ["<library-module-name>"] per le librerie facoltative la definizione Android.bp del modulo. Queste proprietà accettano un elenco dei nomi dei moduli. L'ordine relativo delle librerie nell'elenco deve essere lo stesso dell'ordine nel file manifest.

Per i moduli Android.mk:

  1. Verifica se la libreria ha un nome diverso (nel file manifest) rispetto al nome del modulo (nel sistema di compilazione). In caso affermativo, correggi il problema temporaneamente aggiungendo LOCAL_PROVIDES_USES_LIBRARY := <library-name> nel file Android.mk della libreria o provides_uses_lib: "<library-name>" nel file Android.bp della libreria (entrambi i casi sono possibili poiché un modulo Android.mk potrebbe dipendere da una libreria Android.bp). Per una soluzione a lungo termine, correggi il problema sottostante: rinomina il modulo Libreria.

  2. Aggiungi LOCAL_USES_LIBRARIES := <library-module-name> per le librerie obbligatorie e LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> per le librerie facoltative alla definizione Android.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 build: percorso 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 build: dipendenza libreria mancante

Tentativo di aggiungere <uses-library> X del manifest del modulo Y alla build per Y potrebbe causare un errore di generazione 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 il file manifest La voce <uses-library> è com.android.X, ma il nome del modulo libreria è solo X, causa 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 delle commissioni a livello di categoria 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 formato 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 delle CLC, cerca un comando dexopt per l'errore in maggior dettaglio più avanti in questo modulo. Per risolvere il problema, assicurati che il controllo in fase di build per il 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). La in un sistema di compilazione non gestisce tutti i casi, perché in fase di compilazione è impossibile di sapere con certezza cosa carica l'app in fase di runtime.

Contesto del caricatore di classi

Le commissioni a livello di categoria sono 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. Nella In altre parole, CLC è il grafico delle dipendenze "unfolded" a un albero. La duplicazione è solo a livello logico; gli effettivi caricatori delle classi sottostanti duplicati (in fase di runtime c'è una singola istanza di caricatore delle classi per ogni libreria).

CLC definisce l'ordine di ricerca delle librerie durante la risoluzione delle classi Java utilizzate nella raccolta o nell'app. L'ordine di ricerca è importante perché le librerie possono contenere duplicati e il corso viene risolto nella prima corrispondenza.

CLC sul dispositivo (tempo di esecuzione)

PackageManager (in frameworks/base) crea un CLC per caricare un modulo Java sul dispositivo. Aggiunge le librerie elencate nei tag <uses-library> della sezione come elementi delle commissioni 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 utilizzo è diversa dal suo significato abituale (ad esempio 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 <uses-library> nel file manifest).

In altre parole, esistono due fonti di informazioni che consentono a PackageManager per creare regole di controllo a livello di runtime: <uses-library> tag nel file manifest e delle dipendenze della libreria condivisa nelle configurazioni XML.

CLC on-host (tempo di creazione)

Le commissioni a livello di categoria non sono necessarie solo quando si carica una biblioteca o un'app, ma sono necessarie anche e ne devi compilare una. La compilazione può avvenire sul dispositivo (dexopt) o durante la compilazione (dexpreopt). Dal momento che dexopt viene eseguito sul dispositivo, ha lo stesso informazioni come PackageManager (manifest e dipendenze della libreria condivisa). La dexpreopt, tuttavia, avviene sull'host e in un ambiente completamente diverso e deve ricevere le stesse informazioni dal sistema di compilazione.

Pertanto, le CLC in fase di build utilizzate da dexpreopt e le CLC in fase di esecuzione utilizzate da I valori PackageManager sono uguali, ma vengono 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 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 ha un impatto negativo sul rendimento, in quanto costringe la raccolta o l'app a essere o l'esecuzione senza ottimizzazioni (ad esempio, il codice dell'app potrebbe devono essere estratti 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 creazione: se presente, viene aggiunto all'elenco di controllo delle regole, passato a dex2oat e registrati nel file *.odex. Se una raccolta facoltativa non è presente, viene saltata e non viene aggiunta al CLC. In caso di mancata corrispondenza tra data e ora di build e stato di runtime (la libreria facoltativa è presente in un caso, ma non nell'altro), le CLC in fase di build e di runtime non corrispondono e il codice compilato rifiutato.

Dettagli avanzati del sistema di compilazione (correttore manifest)

A volte i tag <uses-library> mancano nel file manifest di origine di una biblioteca o di 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 quando possibile è preferibile consentire a soong di aggiungere voci manifest automaticamente; è meno soggetta a errori e semplifica la manutenzione. Ad esempio, quando molte app usano un libreria che aggiunge una nuova dipendenza <uses-library>, tutte le app devono essere ed è difficile da gestire.