O Android 12 apresenta mudanças no sistema de compilação 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 compilação podem corromper
os builds. Use esta página para se preparar para falhas e siga as receitas nesta
página para corrigir e reduzir os problemas.
O dexpreopt é o processo de compilação antecipada de bibliotecas e apps Java. O dexpreopt acontece no host durante a criação (ao contrário do dexopt, que acontece no dispositivo). A estrutura das dependências de biblioteca compartilhada usadas por um módulo Java (uma biblioteca ou um app) é conhecida como contexto do carregador de classes (CLC, na sigla em inglês). Para garantir a correção do dexpreopt, os CLCs de build e de tempo de execução precisam coincidir. A CLC de build é o que o compilador dex2oat usa no momento da pré-otimização do dex (ela é gravada nos arquivos ODEX), e a CLC 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 garantir a correção, é necessário processar classes duplicadas. Se as dependências da biblioteca compartilhada em tempo de execução forem diferentes daquelas usadas para compilação, algumas das classes poderão ser resolvidas de maneira diferente, causando bugs sutis em tempo de execução. O desempenho também é afetado pelas verificações de tempo de execução de 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 e de tempo de execução, ele vai rejeitar os artefatos dexpreopt e executar o dexopt. Para inicializações subsequentes, 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 tempo 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.
Alterações interruptivas
O sistema de build precisa conhecer as dependências <uses-library>
antes de gerar regras de build 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 pode ler arquivos arbitrários quando
gera 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
).
Antes, o ART usava uma solução alternativa que ignorava as dependências de biblioteca compartilhada (conhecidas como &-classpath
). Isso não era seguro e causava bugs sutis. Por isso, a solução alternativa foi removida no Android 12.
Como resultado, módulos Java que não fornecem informações corretas de <uses-library>
nos arquivos de build podem causar interrupções de build (causadas por uma incompatibilidade de CLC no momento do build) ou regressões no tempo da 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:
Desative globalmente a verificação no momento da build para um produto específico definindo
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
no makefile do produto. Isso corrige erros de build, exceto em casos especiais, listados na seção Como corrigir falhas. No entanto, essa é uma solução temporária, e pode causar incompatibilidade de CLC no tempo de inicialização, seguida por dexopt.
Corrija os módulos que falharam antes de desativar globalmente a verificação no momento da build adicionando as informações
<uses-library>
necessárias aos arquivos de build (consulte Correção de falhas para mais detalhes). Para a maioria dos módulos, isso exige adicionar algumas linhas emAndroid.bp
ou emAndroid.mk
.Desative a verificação no momento da build e o dexpreopt para os casos problemáticos, por módulo. Desative o dexpreopt para não desperdiçar tempo de build e armazenamento em artefatos que são rejeitados na inicialização.
Reative globalmente a verificação de tempo de build removendo a definição de
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
que foi definida na Etapa 1. O build não vai falhar após essa mudança (devido às etapas 2 e 3).Corrija os módulos desativados na etapa 3, um de cada vez, e reative o dexpreopt e a verificação
<uses-library>
. Envie relatórios de bugs, se necessário.
As verificações de <uses-library>
no momento da build são aplicadas no Android 12.
Corrigir falhas
As seções a seguir mostram como corrigir tipos específicos de falhas.
Erro de build: incompatibilidade de CLC
O sistema de build faz uma verificação de coerência no momento da 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
de um APK, se necessário) e comparar tags <uses-library>
no manifesto
com as informações <uses-library>
nos arquivos de build. Se a verificação falhar, o erro vai aparecer assim:
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 no momento da build ainda é realizada, mas uma falha não significa uma falha de build. Em vez disso, uma falha na verificação faz com que o sistema de build faça downgrade do filtro do compilador dex2oat paraverify
no dexpreopt, o que desativa a compilação AOT totalmente para esse módulo. - Para uma correção rápida e global na 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 de 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ção deAndroidManifest.xml
ou do manifesto dentro de um APK que pode ser verificado com `aapt dump badging $APK | grep uses-library
`.
Para módulos do Android.bp
:
Procure a biblioteca ausente na propriedade
libs
do módulo. Se ele estiver lá, o Soong normalmente adiciona essas bibliotecas automaticamente, exceto nestes casos especiais:- A biblioteca não é uma biblioteca do SDK (é definida como
java_library
em vez dejava_sdk_library
). - A biblioteca tem um nome 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 de SDK ou renomeie o módulo dela.- A biblioteca não é uma biblioteca do SDK (é definida como
Se a etapa anterior não resolveu o problema, 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 do manifesto.
Para módulos do Android.mk
:
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 arquivoAndroid.mk
da biblioteca ou adicioneprovides_uses_lib: "<library-name>"
no 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 subjacente: renomeie o módulo da biblioteca.Adicione
LOCAL_USES_LIBRARIES := <library-module-name>
para as bibliotecas obrigatórias eLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
para as 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 de 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), geralmente a build vai falhar. Se não for possível encontrar um caminho, isso pode indicar que a biblioteca está configurada de
alguma forma inesperada. Corrija temporariamente o build desativando o dexpreopt para o módulo
problemático.
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 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 arquivo
de build de Y pode resultar em um erro de build devido à dependência ausente, X.
Esta é uma mensagem de erro de exemplo para módulos Android.bp:
"Y" depends on undefined module "X"
Esta é uma mensagem de erro de exemplo 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 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 de 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 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 o módulo com falha. Para corrigir isso, verifique se a verificação no momento da build do módulo foi aprovada. Se isso não funcionar, talvez seu caso seja especial e não tenha 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 no momento do build é impossível saber com certeza o que o app carrega no tempo de execução.
Contexto do carregador de classe
O 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 classes personalizadas): é 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 classpath). Cada nó de uma árvore CLC é um nó <uses-library>
que pode ter seus próprios subnós <uses-library>
.
Como as dependências <uses-library>
são um gráfico acíclico dirigido, e não necessariamente uma árvore, o CLC pode conter várias subárvores para a mesma biblioteca. Em outras palavras, o CLC é o gráfico de dependência "desdobrado" em uma árvore. A duplicação
é apenas em um nível lógico. Os carregadores de classe reais não são
duplicados. Em tempo de execução, há uma única instância de carregador de classe para cada biblioteca.
O CLC define a ordem de pesquisa das bibliotecas ao resolver classes Java usadas pela biblioteca ou pelo app. A ordem de pesquisa é importante porque as bibliotecas podem conter classes duplicadas, e a classe é resolvida para a primeira correspondência.
CLC no dispositivo (tempo de execução)
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 CLC de nível superior.
Para cada biblioteca usada, o 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
folha da árvore CLC construída sejam bibliotecas sem dependências
de <uses-library>
.
O PackageManager
só conhece bibliotecas compartilhadas. A definição de "compartilhada" neste uso é diferente do significado usual (como em "compartilhada" x "estática"). 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 DEX JAR 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 durante a execução: tags <uses-library>
no manifesto e
dependências de biblioteca compartilhada em configurações XML.
CLC no host (tempo de 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.
Assim, a CLC de build usada pelo dexpreopt e a CLC de execução usada pelo
PackageManager
são a mesma coisa, mas calculadas de duas maneiras diferentes.
As CLCs de build e de tempo de execução precisam coincidir. Caso contrário, o código compilado com AOT
criado pelo dexpreopt será rejeitado. Para verificar a igualdade dos CLCs de build e
de tempo de execução, o compilador dex2oat registra o CLC 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 da execução é informada no logcat durante a inicialização. Use este comando para pesquisar por ele:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
A incompatibilidade é ruim para a performance, já que força a biblioteca ou o app a serem dexoptados ou executados 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 obrigatória precisa estar presente no momento da criação. A ausência dela é um erro de criação. Uma biblioteca opcional pode estar presente ou ausente
no momento da criação: se estiver presente, ela será adicionada ao CLC, transmitida ao dex2oat e
gravada no arquivo *.odex
. Se uma biblioteca opcional estiver ausente, ela será ignorada e não será adicionada 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 em outro),
as CLCs de build e de execução não vão corresponder, e o código compilado será
rejeitado.
Detalhes avançados do sistema de build (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 incluir essa tag.
O Soong pode calcular algumas das tags <uses-library>
ausentes para 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 calculadas dessa forma, mas, quando possível, é preferível deixar o Soong adicionar entradas de manifesto automaticamente. Isso reduz a probabilidade de 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.