O Android 12 apresenta mudanças no sistema de build para a compilação AOT
de arquivos DEX (dexpreopt) em módulos Java que têm dependências
<uses-library>
. Em alguns casos, essas mudanças no sistema de build podem corromper
os builds. Use esta página para se preparar para falhas e siga as instruções nesta
página para corrigi-las e mitigá-las.
O dexpreopt é o processo de compilação antecipada de bibliotecas e apps Java. O Dexpreopt ocorre no host no momento da criação, ao contrário do dexopt, que acontece no dispositivo. A estrutura de dependências de biblioteca compartilhada usada por um módulo Java (uma biblioteca ou um app) é conhecida como contexto do 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.
Essas CLCs de tempo de build e de execução precisam coincidir por motivos de precisã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 das usadas para compilação, algumas das classes poderão ser resolvidas de forma diferente, causando bugs sutis durante a execução. O desempenho também é afetado pelas verificações de tempo 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 tempo de build e de execução, ele rejeitará os artefatos de dexpreopt e executará o dexopt. Para inicializações seguintes, isso não é um problema, porque os apps podem ser dexoptados 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 em outras bibliotecas Java. O Android tem milhares de apps, e centenas deles usam bibliotecas compartilhadas. Os parceiros também são afetados, já que têm as próprias bibliotecas e apps.
Interromper mudanças
O sistema de build precisa conhecer as dependências <uses-library>
antes de
gerar regras de build de dexpreopt. No entanto, ele não pode acessar o manifesto diretamente
e ler as tags <uses-library>
nele, porque o sistema de build não tem permissão para ler arquivos arbitrários ao
gerar regras de build (por motivos de desempenho). Além disso, o manifesto pode
ser empacotado em um APK ou um pré-build. Portanto, as informações de <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 da 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 informações de <uses-library>
corretas nos arquivos de build podem causar falhas de build (causadas por uma
incompatibilidade de CLC no momento da criação) ou regressões no tempo de primeira inicialização (causadas por uma incompatibilidade de CLC
no momento da inicialização seguida por dexopt).
Caminho de migração
Siga estas etapas para corrigir um build corrompido:
Para desativar globalmente a verificação do tempo de build de um produto específico, defina
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
no makefile do produto. Isso corrige erros de build, exceto para casos especiais, listados na seção Como corrigir falhas. No entanto, essa é uma solução temporária que pode causar incompatibilidade de CLC no momento da inicialização seguida de dexopt.
Corrija os módulos que falharam antes de você desativar globalmente a verificação de tempo de build adicionando as informações
<uses-library>
necessárias aos arquivos de build. Consulte Como corrigir falhas para mais detalhes. Para a maioria dos módulos, isso requer a adição de algumas linhas emAndroid.bp
ouAndroid.mk
.Desative a verificação de tempo de build e a descontinuação para os casos problemáticos por módulo. Desative o dexpreopt para não perder tempo de build e armazenamento em artefatos que são rejeitados na inicialização.
Reative a verificação de tempo de build globalmente ao desmarcar
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
que foi definido na etapa 1. O build não vai falhar após essa mudança (por causa das etapas 2 e 3).Corrija os módulos que você desativou na Etapa 3, um de cada vez, e reative o dexpreopt e a verificação
<uses-library>
. Registre bugs, se necessário.
As verificações de <uses-library>
no momento da criação são aplicadas no Android 12.
Corrigir falhas
As seções a seguir mostram 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 pode ler
o manifesto, mas pode gerar regras de build para ler o manifesto (extraindo-o
de um APK, se necessário) e comparar tags <uses-library>
no manifesto
com as informações de <uses-library>
nos arquivos de build. Se a verificação falhar,
o erro será parecido com 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 da 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 do tempo de build ainda é executada, mas uma falha de verificação não significa uma falha no build. Em vez disso, uma falha de verificação faz com que o sistema de build faça o downgrade do filtro do compilador dex2oat paraverify
no dexpreopt, o que desativa totalmente a compilação AOT para esse 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
. Ele tem o mesmo efeito quePRODUCT_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 de correção da causa raiz do erro, informe ao sistema de build as
tags
<uses-library>
no manifesto. Uma inspeção da mensagem de erro mostra quais bibliotecas causam o problema, assim como a inspeçãoAndroidManifest.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
:
Procure a biblioteca ausente na propriedade
libs
do módulo. Se estiver presente, o Soong normalmente vai adicionar essas bibliotecas automaticamente, exceto nestes casos especiais:- Ela não é uma biblioteca do SDK. Ela é definida como
java_library
em vez dejava_sdk_library
. - A biblioteca tem um nome de biblioteca diferente (no manifesto) do nome do módulo (no sistema de build).
Para corrigir isso temporariamente, adicione
provides_uses_lib: "<library-name>"
na definição da bibliotecaAndroid.bp
. Para uma solução de longo prazo, corrija o problema subjacente: converta a biblioteca em uma biblioteca do SDK ou renomeie o módulo.- Ela não é uma biblioteca do SDK. Ela é definida como
Se a etapa anterior não fornecer uma resolução, adicione
uses_libs: ["<library-module-name>"]
para bibliotecas obrigatórias ouoptional_uses_libs: ["<library-module-name>"]
para bibliotecas opcionais à definiçãoAndroid.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
:
Confira se a biblioteca tem um nome de biblioteca diferente (no manifesto) do nome do módulo (no sistema de build). Se for o caso, corrija isso temporariamente adicionando
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
ao arquivoAndroid.mk
da biblioteca ou adicioneprovides_uses_lib: "<library-name>"
ao arquivoAndroid.bp
da biblioteca. Os dois casos são possíveis, já que um móduloAndroid.mk
pode depender de uma bibliotecaAndroid.bp
. Para uma solução de longo prazo, corrija o problema: renomeie o módulo da biblioteca.Adicione
LOCAL_USES_LIBRARIES := <library-module-name>
para bibliotecas necessárias. AdicioneLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
para bibliotecas opcionais à definiçãoAndroid.mk
do módulo. Essas propriedades 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 da biblioteca desconhecido
Se o sistema de build não encontrar um caminho para um jar DEX <uses-library>
(um
caminho de build no host ou um caminho de instalação no dispositivo), o build
geralmente falha. Uma falha ao encontrar um caminho pode indicar que a biblioteca está configurada de
alguma maneira inesperada. Corrija o build temporariamente 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
Registre um bug para investigar cenários sem suporte.
Erro de build: dependência da biblioteca ausente
Uma tentativa de adicionar <uses-library>
X do manifesto do módulo Y ao arquivo
de build para Y pode resultar em um erro de build devido à ausência da dependência X.
Este é 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 é nomeada de maneira diferente do
módulo correspondente 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 chamado 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 para bibliotecas Android.mk (variável do módulo):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Incompatibilidade de CLC no tempo de inicialização
Na primeira inicialização, pesquise no logcat mensagens relacionadas à incompatibilidade do 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 o módulo com defeito. 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 processa todos os casos porque é impossível 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 os próprios subnós <uses-library>
.
Como as dependências <uses-library>
são um gráfico acíclico direcionado e não
necessariamente uma árvore, a CLC pode conter várias subárvores para a mesma biblioteca. Em
outras palavras, a CLC é o gráfico de dependência "desdobrado" em uma árvore. A duplicação
ocorre apenas em um nível lógico. Os carregadores de classe subjacentes reais não são
duplicados (durante a 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 pela biblioteca ou app. Essa ordem é importante porque as bibliotecas podem conter classes duplicadas e a classe é resolvida para a primeira correspondência.
CLC no dispositivo (tempo de execução)
PackageManager
(em frameworks/base
) cria uma CLC para carregar um módulo Java
no dispositivo. Ele adiciona as bibliotecas listadas nas tags <uses-library>
do manifesto
do módulo como elementos CLC de nível superior.
Para cada biblioteca usada, a PackageManager
recebe todas as dependências <uses-library>
(especificadas como tags no manifesto dessa biblioteca) e adiciona um
CLC aninhado para cada dependência. Esse processo continua recursivamente até que todos
os nós de folha da árvore CLC construída sejam bibliotecas sem dependências de
<uses-library>
.
PackageManager
só reconhece bibliotecas compartilhadas. A definição de "compartilhamento"
é diferente do significado comum (como compartilhado versus estático). No Android,
as bibliotecas compartilhadas Java são aquelas listadas em configurações XML instaladas
no dispositivo (/system/etc/permissions/platform.xml
). Cada entrada contém o nome
de uma biblioteca compartilhada, um caminho para o arquivo jar DEX e uma lista de dependências
(outras bibliotecas compartilhadas que essa usa no tempo de execução e especifica em
tags <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)
O CLC não é necessário apenas ao carregar uma biblioteca ou um app, mas também ao
compilar um. A compilação pode acontecer 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).
No entanto, o Dexpreopt ocorre no host e em um ambiente
totalmente diferente e precisa receber as mesmas informações do sistema de build.
Portanto, o CLC no tempo de build usado por dexpreopt e o CLC no tempo de execução usado por
PackageManager
são a mesma coisa, mas calculados de duas maneiras diferentes.
As CLCs de ambiente de build e de execução precisam coincidir. Caso contrário, o código compilado AOT
criado pelo dexpreopt vai ser rejeitado. Para verificar a igualdade de CLCs de tempo de build e
de execução, o compilador dex2oat registra CLCs de tempo de build 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 de CLC no momento da criação e do tempo de execução é relatada no logcat durante a inicialização. Pesquise com este comando:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
A incompatibilidade é ruim para o desempenho, 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. Do
ponto de vista do dexpreopt, uma biblioteca necessária precisa estar presente no tempo de build (a
ausência dela é um erro de build). Uma biblioteca opcional pode estar presente ou ausente
no momento do build: se presente, ela é adicionada ao CLC, transmitida para o dex2oat e
gravada no arquivo *.odex
. Se não houver uma biblioteca opcional, ela será ignorada e não será adicionada à 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 automaticamente algumas das tags <uses-library>
ausentes para uma determinada biblioteca
ou app, já que as bibliotecas do SDK no fechamento de dependências transitivo
da biblioteca ou do app são necessários. O fechamento é necessário porque a biblioteca (ou app) pode
depender de uma biblioteca estática que depende de uma biblioteca de SDK e, possivelmente,
pode depender de novo de maneira transitiva por outra biblioteca.
Nem todas as tags <uses-library>
podem ser calculadas dessa maneira, mas, quando possível, é
preferível permitir que o Soong adicione entradas de manifesto automaticamente. Esse método é 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.