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
interromper os 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 e apps Java. O Dexpreopt ocorre no host no momento da build, ao contrário do dexopt, que acontece no dispositivo. A estrutura de dependências de bibliotecas compartilhadas usada por um módulo Java (uma biblioteca ou um app) é conhecida como contexto do carregador de classes (CLC). Para garantir a correção de 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 garantir a correção, é necessário processar 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 é aceitável 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 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 de <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 tem permissão para 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 <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, módulos Java que não fornecem informações <uses-library>
corretas nos arquivos de build podem causar quebras de build (causadas por uma
incompatibilidade de CLC no momento do build) ou regressões no tempo de inicialização (causadas por uma
incompatibilidade de CLC no momento da inicialização seguida de dexopt).
Caminho de migração
Siga estas etapas para corrigir um build corrompido:
Desativar globalmente a verificação no tempo de build para um produto específico definindo
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
no makefile do produto. Isso corrige erros de build, exceto casos especiais, listados 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.
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 exige a adição de algumas linhas emAndroid.bp
ou emAndroid.mk
.Desative a verificação no build e o dexpreopt 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 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).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 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 as tags <uses-library>
no manifesto
com as informações <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 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 build ainda é realizada, mas uma falha na verificação não significa um erro 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 que oPRODUCT_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:- A biblioteca não é uma biblioteca do SDK. Ela é 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 do SDK ou renomeie o módulo.- A biblioteca 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
:
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. Ambos os 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 obrigatórias eLOCAL_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 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), o build
geralmente falha. A falha na localização de um caminho pode indicar que a biblioteca está configurada 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 arquivo
de build de Y pode resultar em um erro de build devido à dependência ausente, X.
Este é um exemplo de mensagem de erro para módulos Android.bp:
"Y" depends on undefined module "X"
Confira um exemplo de mensagem de erro para módulos do 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 de 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 momento da 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 defeituoso. 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 no momento do build é impossível saber com certeza o que o app carrega no momento da execução.
Contexto do carregador de classe
O CLC é uma estrutura em árvore que descreve a hierarquia do class loader. 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
outras palavras, o CLC é o gráfico de dependência "desdobrado" para uma árvore. A duplicação
é apenas em um nível lógico. Os carregadores de classe subjacentes reais não são
duplicados (no momento da 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)
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, 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>
.
O PackageManager
só reconhece bibliotecas compartilhadas. A definição de "compartilhado" nesse uso é diferente do significado usual (como "compartilhado" e "estático"). No Android,
as bibliotecas compartilhadas Java são aquelas listadas em configurações XML que são 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 ambiente 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 tempo de 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).
O Dexpreopt, no entanto, 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 pelo dexpreopt e o CLC no tempo de execução usado pelo
PackageManager
são a mesma coisa, mas calculados 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 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 do CLC no momento 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. Do
ponto de vista do dexpreopt, uma biblioteca obrigatória precisa estar presente no momento do build. A
ausência dela é um erro de build. Uma biblioteca opcional pode estar presente ou ausente
no momento do build: se estiver presente, ela será adicionada ao CLC, transmitida para dex2oat e
gravada no arquivo *.odex
. Se uma biblioteca opcional estiver ausente, 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 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 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 de <uses-library>
, todos os apps precisam ser
atualizados, o que é difícil de manter.