Verificações dexpreopt e <uses-library>

O sistema de build do Android 12 muda para a compilação AOT dos arquivos DEX (dexpreopt) para módulos Java que têm <uses-library> dependências. Em alguns casos, essas mudanças no sistema de build podem ser corrompidas builds. Use esta página para se preparar para interrupções e siga as receitas nela para corrigir e reduzir as interrupções.

O dexpreopt é o processo de compilação antecipada de bibliotecas Java e apps. O dexpreopt acontece no host durante a compilação (diferente do dexopt, que acontece no dispositivo). A estrutura das dependências de bibliotecas compartilhadas usadas por um (uma biblioteca ou app) é conhecido como seu contexto de carregador de classe (CLC, na sigla em inglês). Para garantir a correção do dexpreopt, os CLCs de build-time e run-time precisam coincidir. O CLC no build é o que o compilador dex2oat usa no momento dexpreopt (ele é registrado nos arquivos ODEX), e o CLC no tempo de execução é o contexto em que o código pré-compilado é carregado no dispositivo.

Esses CLCs de build e de execução precisam coincidir por motivos de correção e desempenho. Para correção, é necessário lidar com classes duplicadas. Se as dependências da biblioteca compartilhada no momento da execução forem diferentes daquelas usadas para a compilação, algumas das classes poderão ser resolvidas de maneira diferente, causando bugs sutis no momento da execução. O desempenho também é afetado pelas verificações de execução para classes duplicadas.

Casos de uso afetados

A primeira inicialização é o principal caso de uso afetado por essas mudanças: se o ART detectar uma incompatibilidade entre CLCs de build-time e run-time, ele rejeitará artefatos dexpreopt e executará dexopt. Isso não é um problema para inicializações subsequentes, porque os apps podem ser desativados em segundo plano e armazenados no disco.

Áreas afetadas do Android

Isso afeta todos os apps e bibliotecas Java que têm dependências de ambiente de execução outras bibliotecas Java. O Android tem milhares de apps, e centenas deles usam bibliotecas compartilhadas. Os parceiros também são afetados, pois têm suas próprias funções bibliotecas e apps do Google.

Alterações de intervalo

O sistema de build precisa conhecer as dependências <uses-library> antes de gera regras de build de dexpreopt. No entanto, ele não pode acessar o manifesto diretamente e leia o <uses-library> porque o sistema de build não tem permissão para ler arquivos arbitrários ele gera regras de build (por motivos de desempenho). Além disso, o manifesto pode ser empacotados em um APK ou pré-criado. Portanto, as informações <uses-library> precisam estar presentes nos arquivos de build (Android.bp ou Android.mk).

Anteriormente, o ART usava uma solução alternativa que ignorava as dependências de biblioteca compartilhada (conhecida como &-classpath). Isso não era seguro e causava bugs sutis. Por isso, a solução alternativa foi removida no Android 12.

Como resultado, os módulos Java que não fornecem o <uses-library> correto informações nos arquivos de build podem causar falhas de build (causadas por uma incompatibilidade de CLC durante a criação) ou regressões do tempo de primeira inicialização (causadas por um tempo de inicialização incompatibilidade de CLC seguida por dexopt).

Caminho de migração

Siga estas etapas para corrigir um build corrompido:

  1. Desativar globalmente a verificação no tempo de build para um produto específico por configuração

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    no makefile do produto. Isso corrige erros de build (exceto em casos especiais, listadas na seção Como corrigir falhas). No entanto, essa é uma solução temporária e pode causar uma incompatibilidade de CLC no momento da inicialização seguida por dexopt.

  2. Corrija os módulos que falharam antes de desativar globalmente a verificação no build adicionando as informações <uses-library> necessárias aos arquivos de build. Consulte Como corrigir falhas para saber mais. Para a maioria dos módulos, isso requer a adição de algumas linhas em Android.bp ou Android.mk.

  3. Desativar a verificação de tempo de build e dexpreopt para os casos problemáticos, por módulo. Desative o dexpreopt para não perder tempo de build e armazenamento em artefatos rejeitados na inicialização.

  4. Reativar globalmente a verificação no build desativando PRODUCT_BROKEN_VERIFY_USES_LIBRARIES que foi definido na etapa 1. O build não deve falhar após essa mudança (devido às etapas 2 e 3).

  5. Corrija os módulos desativados na etapa 3, um por vez, e reative o dexpreopt e a verificação <uses-library>. Registre bugs, se necessário.

As verificações <uses-library> no tempo de build são aplicadas no Android 12.

Corrigir falhas

As seções a seguir explicam como corrigir tipos específicos de falha.

Erro de build: incompatibilidade de CLC

O sistema de build faz uma verificação de coerência no tempo de build entre as informações nos arquivos Android.bp ou Android.mk e o manifesto. O sistema de build não consegue ler o manifesto, mas pode gerar regras de build para ler o manifesto (extraindo a partir de um APK, se necessário) e compare as tags <uses-library> no manifesto. com as informações de <uses-library> nos arquivos de build. Se a verificação falhar, o erro será semelhante a este:

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

Como a mensagem de erro sugere, há várias soluções, dependendo urgência:

  • Para uma correção temporária em todo o produto, defina PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true no makefile do produto. A verificação de coerência no build ainda é realizada, mas uma falha na verificação não significa uma falha no build. Em vez disso, uma falha na verificação faz o sistema de compilação fazer downgrade O compilador dex2oat filtra para verify no dexpreopt, que desativa a compilação AOT. inteiramente neste módulo.
  • Para uma correção rápida e global de linha de comando, use a variável de ambiente RELAX_USES_LIBRARY_CHECK=true Tem o mesmo efeito que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, mas destinado ao uso na linha de comando. A variável de ambiente substitui a variável do produto.
  • Para uma solução para corrigir a causa raiz do erro, informe o sistema de compilação das tags <uses-library> no manifesto. Uma inspeção da mensagem de erro mostra quais bibliotecas causam o problema, assim como a inspeção AndroidManifest.xml ou o manifesto dentro de um APK que pode ser verificado com "aapt dump badging $APK | grep uses-library".

Para módulos Android.bp:

  1. Procure a biblioteca ausente na propriedade libs do módulo. Se for O Soong normalmente adiciona essas bibliotecas automaticamente, exceto nos casos casos especiais:

    • A biblioteca não é uma biblioteca do SDK. Ela é definida como java_library em vez de java_sdk_library.
    • A biblioteca tem um nome de biblioteca diferente (no manifesto) de seu módulo no sistema de build.

    Para corrigir isso temporariamente, adicione provides_uses_lib: "<library-name>" na definição da biblioteca Android.bp. Para uma solução de longo prazo, corrija problema: converter a biblioteca para uma biblioteca do SDK ou renomear o módulo dela.

  2. Se a etapa anterior não fornecer uma resolução, adicione uses_libs: ["<library-module-name>"] para bibliotecas obrigatórias ou optional_uses_libs: ["<library-module-name>"] para bibliotecas opcionais à definição Android.bp do módulo. Essas propriedades aceitam uma lista de nomes de módulos. A ordem relativa das bibliotecas na lista precisa ser a mesma que a ordem no manifesto.

Para módulos Android.mk:

  1. Verifique se a biblioteca tem um nome diferente (no manifesto) do nome do módulo (no sistema de build). Se isso acontecer, corrija temporariamente adicionando LOCAL_PROVIDES_USES_LIBRARY := <library-name> no arquivo Android.mk da biblioteca ou adicione provides_uses_lib: "<library-name>" no arquivo Android.bp da biblioteca. Ambos os casos são possíveis, já que um módulo Android.mk pode depender de uma biblioteca Android.bp. Para uma solução de longo prazo, corrija o problema subjacente: renomeie o módulo da biblioteca.

  2. Adicione LOCAL_USES_LIBRARIES := <library-module-name> aos campos obrigatórios bibliotecas adicionar LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> para bibliotecas opcionais à definição Android.mk do módulo. Esses aceitam uma lista de nomes de módulos. A ordem relativa das bibliotecas na lista precisa ser a mesma do manifesto.

Erro de build: caminho de biblioteca desconhecido

Se o sistema de compilação não conseguir encontrar um caminho para um jar DEX <uses-library> (um arquivo de tempo de build no host ou um caminho de instalação no dispositivo), ele geralmente falha ser construído. Uma falha ao encontrar um caminho pode indicar que a biblioteca está configurada em de forma inesperada. Corrija temporariamente o build desativando o dexpreopt para o módulo com problemas.

Android.bp (propriedades do módulo):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (variáveis de módulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Informe um bug para investigar os cenários sem suporte.

Erro de build: dependência de biblioteca ausente

Uma tentativa de adicionar <uses-library> X do manifesto do módulo Y ao build para Y pode resultar em um erro de compilação devido à dependência ausente, X.

Confira um exemplo de mensagem de erro para módulos Android.bp:

"Y" depends on undefined module "X"

Este é um exemplo de mensagem de erro para módulos 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

Uma fonte comum desses erros é quando uma biblioteca tem um nome diferente módulo correspondente é nomeado no sistema de build. Por exemplo, se a entrada <uses-library> do manifesto for com.android.X, mas o nome do módulo da biblioteca for apenas X, isso vai causar um erro. Para resolver esse caso, informe ao sistema de build que o módulo com o nome X fornece um <uses-library> chamado com.android.X.

Este é um exemplo para bibliotecas Android.bp (propriedade do módulo):

provides_uses_lib: “com.android.X”,

Este é um exemplo de bibliotecas Android.mk (variável de módulo):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Incompatibilidade de CLC no tempo de inicialização

Na primeira inicialização, procure no logcat mensagens relacionadas à incompatibilidade de CLC, conforme mostrado abaixo:

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

A saída pode ter mensagens do formulário mostrado aqui:

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Se você receber um aviso de incompatibilidade de CLC, procure um comando dexopt para mais tarde neste módulo. Para corrigir isso, verifique se a verificação no tempo de build do módulo é bem-sucedida. Se isso não funcionar, talvez o seu seja um caso especial que não tem suporte do sistema de build (como um app que carrega outro APK, não uma biblioteca). O sistema de build não lida com todos os casos porque, no momento da criação, é impossível para saber com certeza o que o app carrega durante a execução.

Contexto do carregador de classes

A CLC é uma estrutura semelhante a uma árvore que descreve a hierarquia do carregador de classes. O sistema de build usa o CLC em um sentido restrito (ele abrange apenas bibliotecas, não APKs ou carregadores de classe personalizados): é uma árvore de bibliotecas que representa o fechamento transitivo de todas as dependências <uses-library> de uma biblioteca ou app. Os elementos de nível superior de um CLC são as dependências <uses-library> diretas especificadas no manifesto (o caminho de classe). Cada nó de uma árvore de CLC é um nó <uses-library> que pode ter seus próprios subnós <uses-library>.

Como as dependências de <uses-library> são um gráfico acíclico dirigido, e não necessariamente uma árvore, a CLC pode conter vários subárvores para a mesma biblioteca. Em em outras palavras, a CLC é o gráfico de dependência "desdobrado" para uma árvore. A duplicação está apenas em um nível lógico: os carregadores de classes reais não são duplicado (durante o tempo de execução há uma única instância de carregador de classe para cada biblioteca).

A CLC define a ordem de pesquisa das bibliotecas ao resolver classes Java usadas por biblioteca ou app. A ordem da pesquisa é importante porque as bibliotecas podem conter classes duplicadas, e a classe é resolvida para a primeira correspondência.

CLC no dispositivo (ambiente de execução)

PackageManager (em frameworks/base) cria um CLC para carregar um módulo Java no dispositivo. Ele adiciona as bibliotecas listadas nas tags <uses-library> no manifesto do módulo como elementos de CLC de nível superior.

Para cada biblioteca usada, a PackageManager recebe todos os <uses-library> de dependências (especificadas como tags no manifesto dessa biblioteca) e adiciona uma a CLC aninhada para cada dependência. Esse processo continua recursivamente até que Os nós de folha da árvore de CLC construída são bibliotecas sem <uses-library> dependências.

PackageManager só reconhece bibliotecas compartilhadas. A definição de "compartilhado" nesse uso é diferente do significado usual (como "compartilhado" e "estático"). No Android, Bibliotecas compartilhadas Java são aquelas listadas nas configurações XML que estão instaladas no dispositivo (/system/etc/permissions/platform.xml). Cada entrada contém o nome de uma biblioteca compartilhada, um caminho para o respectivo arquivo jar DEX e uma lista de dependências (outras bibliotecas compartilhadas que este usa no tempo de execução e especifica em <uses-library> no manifesto).

Em outras palavras, há duas fontes de informações que permitem que PackageManager construa o CLC no momento da execução: tags <uses-library> no manifesto e dependências de biblioteca compartilhada em configurações XML.

CLC no host (no build)

A CLC não é necessária apenas ao carregar uma biblioteca ou um aplicativo, ela também é necessária ao e compilar um. A compilação pode ocorrer no dispositivo (dexopt) ou durante o build (dexpreopt). Como o dexopt ocorre no dispositivo, ele tem as mesmas informações que PackageManager (manifestos e dependências de biblioteca compartilhada). O Dexpreopt, no entanto, ocorre no host e em um ambiente e precisa das mesmas informações do sistema de build.

Assim, a CLC do tempo de compilação usada pelo dexpreopt e a CLC no ambiente de execução usadas pelo PackageManager são a mesma coisa, mas computadas de duas maneiras diferentes.

Os CLCs no momento do build e da execução precisam coincidir. Caso contrário, o código compilado pelo AOT criado pelo dexpreopt será rejeitado. Para verificar a igualdade entre tempo de build e CLCs em tempo de execução, o compilador dex2oat registra CLCs de tempo de compilação nos arquivos *.odex. (no campo classpath do cabeçalho do arquivo OAT). Para encontrar o CLC armazenado, use este comando:

oatdump --oat-file=<FILE> | grep '^classpath = '

A incompatibilidade do CLC no tempo de build e de execução é informada no logcat durante a inicialização. Pesquise por ele com este comando:

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

A incompatibilidade é ruim para a performance, porque força a biblioteca ou o app a ser dexoptado ou a ser executado sem otimizações. Por exemplo, o código do app pode precisar ser extraído na memória do APK, uma operação muito cara.

Uma biblioteca compartilhada pode ser opcional ou obrigatória. De para o ponto de vista de dexpreopt, uma biblioteca obrigatória deve estar presente no tempo de compilação (sua a ausência é um erro de build). Uma biblioteca opcional pode estar presente ou ausente no momento da criação: se presente, é adicionado ao CLC, passado para dex2oat e gravado no arquivo *.odex. Se não houver uma biblioteca opcional, ela será ignorada e não é adicionado ao CLC. Se houver uma incompatibilidade entre o status de build e de execução (a biblioteca opcional está presente em um caso, mas não no outro), os CLCs de build e de execução não vão corresponder, e o código compilado será rejeitado.

Detalhes do sistema de compilação avançado (corretor de manifesto)

Às vezes, as tags <uses-library> estão ausentes do manifesto de origem de uma biblioteca ou um app. Isso pode acontecer, por exemplo, se uma das dependências transitivas da biblioteca ou do app começar a usar outra tag <uses-library> e o manifesto da biblioteca ou do app não for atualizado para incluí-la.

O Soong pode calcular algumas das tags <uses-library> ausentes de uma determinada biblioteca ou app automaticamente, como as bibliotecas do SDK no fechamento de dependência transitiva da biblioteca ou do app. O fechamento é necessário porque a biblioteca (ou o app) pode depender de uma biblioteca estática que depende de uma biblioteca do SDK e, possivelmente, pode depender transitivamente de outra biblioteca.

Nem todas as tags <uses-library> podem ser computadas dessa maneira, mas, quando possível, é preferível permitir que o Soong adicione entradas de manifesto automaticamente. Isso é menos propenso a erros e simplifica a manutenção. Por exemplo, quando muitos apps usam uma biblioteca estática que adiciona uma nova dependência <uses-library>, todos os apps precisam ser atualizados, o que é difícil de manter.