Dexpreopt e <uses-library> controlli

Android 12 presenta modifiche di sistema alla compilazione AOT di file DEX (dexpreopt) per i moduli Java che hanno dipendenze <uses-library>. In alcuni casi queste modifiche al sistema possono interrompere le build. Utilizza questa pagina per prepararti alle interruzioni e segui le ricette riportate per correggerle e ridurle al minimo.

Dexpreopt è il processo di compilazione anticipata di librerie e app Java. Il dexpreopt viene eseguito sull'host al momento della creazione (a differenza di dexopt, che viene eseguito sul dispositivo). La struttura delle dipendenze della libreria condivisa utilizzata da un modulo Java (una libreria o un'app) è nota come contesto del caricatore di classi (CLC). Per garantire la correttezza delle commissioni di esecuzione, le regole in tempo di creazione e in fase di esecuzione devono coincidere. Le CLC in fase di build sono utilizzate dal compilatore dex2oat al momento dell'expreopt (sono registrate nei file ODEX), mentre le CLC in fase di esecuzione sono il contesto in cui il codice precompilato viene caricato sul dispositivo.

Questi CLC in fase di build e di runtime devono coincidere per motivi sia di correttezza che di prestazioni. Per la 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 lievi bug di runtime. Le prestazioni sono influenzate anche dai controlli di runtime per le classi duplicate.

Casi d'uso interessati

Il primo avvio è il caso d&#39;uso principale interessato da queste modifiche: se ART rileva una mancata corrispondenza tra i CLC di compilazione e di runtime, 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 interessa tutte le app e le librerie Java che hanno dipendenze di runtime su altre librerie Java. Android ha migliaia di app e centinaia di queste utilizzano librerie condivise. Sono interessati anche i partner, che 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 regole di build (per motivi di prestazioni). Inoltre, il file manifest potrebbe essere impacchettato all'interno di un APK o di un prebuilt. Di conseguenza, le informazioni su <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 della libreria condivisa (nota come &-classpath). Questo non era sicuro e causava lievi bug, pertanto la soluzione alternativa è stata rimossa in Android 12.

Di conseguenza, i moduli Java che non forniscono informazioni <uses-library> corrette nei file di build possono causare malfunzionamenti della build (causati da una mancata corrispondenza delle CLC in fase di build) o regressione del tempo di primo avvio (causate da una mancata corrispondenza CLC all'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. Corregge gli errori di generazione (tranne i casi speciali, elencati nella sezione Correzione dei malfunzionamenti). Tuttavia, questa è una soluzione alternativa temporanea e può causare una mancata corrispondenza delle commissioni di controllo a livello di avvio, seguita da dexopt.

  2. Correggi i moduli che non sono riusciti prima della disattivazione globale del controllo in fase di build aggiungendo le informazioni <uses-library> necessarie ai relativi file di build (per maggiori dettagli, consulta la sezione Correzione dei malfunzionamenti). Per la maggior parte dei moduli, è necessario aggiungere alcune righe in Android.bp o in Android.mk.

  3. Disattiva il controllo in fase di compilazione e dexpreopt per i casi problematici, su base di modulo. Disabilita dexpreopt per non sprecare tempo di compilazione e archiviazione su artefatti che vengono rifiutati all'avvio.

  4. 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).

  5. Correggi i moduli che hai disattivato nel passaggio 3, uno alla volta, quindi riattiva dexpreopt e il controllo <uses-library>. Segnala bug, se necessario.

In Android 12 vengono applicati i controlli <uses-library> in fase di build.

Correggi i malfunzionamenti

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 ha esito negativo, l'errore sarà il 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 diverse soluzioni a seconda dell'urgenza:

  • Per una correzione temporanea a livello di prodotto, imposta PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true nel file marca del prodotto. Il controllo della coerenza in fase di build viene comunque eseguito, ma un errore del controllo non significa un errore della build. Un errore di controllo comporta invece il downgrade del sistema di build del filtro del compilatore dex2oat a verify in dexpreopt, che disabilita la compilazione AOT completamente 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 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 consapevole dei 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 del manifest all'interno di un APK che può essere controllato con "aapt dump badging $APK | grep uses-library").

Per i moduli Android.bp:

  1. Cerca la libreria mancante nella proprietà libs del modulo. Se è presente, di solito le librerie sono inserite automaticamente, tranne che nei seguenti 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 libreria Android.bp. Per una soluzione a lungo termine, risolvi il problema di base: converti la libreria in una libreria SDK o rinomina il relativo modulo.

  2. Se il passaggio precedente non ha risolto il problema, aggiungi uses_libs: ["<library-module-name>"] per le librerie obbligatorie o optional_uses_libs: ["<library-module-name>"] per le librerie facoltative alla definizione Android.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:

  1. Controlla se la libreria ha un nome di libreria diverso (nel file manifest) rispetto al nome del modulo (nel sistema di build). In caso affermativo, correggi il problema temporaneamente aggiungendo LOCAL_PROVIDES_USES_LIBRARY := <library-name> al file Android.mk della libreria o aggiungi provides_uses_lib: "<library-name>" al 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, risolvi il problema di fondo: rinomina il modulo della 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. L'impossibilità di trovare un percorso può indicare che la libreria è configurata in un modo inaspettato. 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 indagare su eventuali scenari non supportati.

Errore di build: dipendenza libreria mancante

Un tentativo di aggiungere <uses-library> X dal manifest del modulo Y al file di build per Y potrebbe causare un errore di build a causa della dipendenza mancante, ovvero 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

Un'origine comune di questi errori si verifica quando una libreria ha un nome diverso rispetto al modulo corrispondente nel sistema di build. Ad esempio, se la voce <uses-library> del file manifest è com.android.X, ma il nome del modulo libreria è solo X, causa un errore. Per risolvere questo caso, comunica 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”,

Ecco 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 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 commissioni a livello di categoria, cerca un comando dexopt per il modulo guasto. 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 build (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 build è impossibile sapere con certezza ciò che l'app carica 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 le regole di conversione a livello di account 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 una CLC sono le dipendenze <uses-library> dirette specificate nel manifest (classpath). 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 diretto aciclico e non necessariamente un albero, le CLC possono contenere più sottoalberi per la stessa libreria. In altre parole, il CLC è il grafico delle dipendenze &quot;aperto&quot; in un albero. La duplicazione è solo a livello logico; gli effettivi caricatori di classi sottostanti non sono duplicati (in fase di runtime c'è una singola istanza di caricatore di classi per ogni libreria).

CLC definisce l&#39;ordine di ricerca delle librerie durante la risoluzione delle classi Java utilizzate dalla libreria o dall&#39;app. L&#39;ordine di ricerca è importante perché le librerie possono contenere classi duplicate e la classe viene risolta 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> nel manifest del modulo come elementi CLC di primo livello.

Per ogni libreria utilizzata, PackageManager ottiene tutte le sue <uses-library> dipendenze (specificate come tag nel manifest di quella libreria) e aggiunge CLC nidificate per ogni dipendenza. Questo processo continua in modo ricorsivo finché tutti i nodi foglia dell'albero CLC creato non sono librerie senza dipendenze <uses-library>.

PackageManager è a conoscenza solo delle raccolte 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 regole di base in fase di runtime: tag <uses-library> nel file manifest e dipendenze della libreria condivisa nelle configurazioni XML.

CLC on-host (in fase di compilazione)

Le commissioni a livello di categoria non sono necessarie solo per caricare una libreria o un'app, ma sono necessarie anche per compilarne una. La compilazione può avvenire sul dispositivo (dexopt) o durante la creazione (dexpreopt). Poiché dexopt viene eseguito sul dispositivo, ha le stesse informazioni di PackageManager (manifest e dipendenze della libreria condivisa). 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.

Le CLC in fase di build e in fase di esecuzione devono coincidere, altrimenti il codice compilato da AOT creato da dexpreopt viene rifiutato. Per verificare l'uguaglianza delle CLC in fase di build e di runtime, il compilatore dex2oat registra le CLC in fase di build 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 tra CLC in fase di build e in fase di runtime 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&#39;app a essere ritirata o a funzionare senza ottimizzazioni (ad esempio, il codice dell&#39;app potrebbe dover essere estratto in memoria dall&#39;APK, un&#39;operazione molto costosa).

La 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 aggiunta all'elenco di controllo delle transazioni, passata a dex2oat e registrata nel file *.odex. Se una raccolta facoltativa non è presente, viene saltata e non viene aggiunta al CLC. Se si verifica una mancata corrispondenza tra lo stato del momento della build e quello del runtime (la libreria facoltativa è presente in un caso, ma non nell'altro), le regole di controllo delle regole di runtime in fase di build e di runtime non corrispondono e il codice compilato viene rifiutato.

Dettagli avanzati del sistema di compilazione (manifest fixer)

A volte nel manifest di origine di una libreria o app mancano i tag <uses-library>. Questo 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 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 Takeg di aggiungere automaticamente le voci manifest. Inoltre, è 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.