Esta página explica como seu aplicativo pode usar as novas funcionalidades do sistema operacional ao ser executado em versões do SO, preservando a compatibilidade com dispositivos mais antigos.
Por padrão, as referências às APIs do NDK no seu app são referências fortes. O carregador dinâmico do Android os resolverá prontamente quando a biblioteca for carregado. Se os símbolos não forem encontrados, o app será cancelado. Isso é contrário à como o Java se comporta, em que uma exceção não será lançada até que a API ausente seja chamou.
Por esse motivo, o NDK impede que você crie referências fortes a
APIs mais recentes do que a minSdkVersion
do app. Isso protege você de
envio acidental de código que funcionou durante o teste, mas não foi carregado
(UnsatisfiedLinkError
será gerado de System.loadLibrary()
) em versões mais antigas
dispositivos. Por outro lado, é mais difícil escrever códigos que usem APIs
mais recente do que a minSdkVersion
do app, porque é necessário chamar as APIs usando
dlopen()
e dlsym()
em vez de uma chamada de função normal.
A alternativa ao uso de referências fortes é usar referências fracas. Um ponto fraco
referência que não é encontrada quando a biblioteca carregou resultados no endereço de
o símbolo seja definido como nullptr
em vez de falhar no carregamento. Eles ainda
não podem ser chamados com segurança, mas contanto que os sites de chamadas sejam protegidos para impedir chamadas
a API quando ela não estiver disponível, o restante do código poderá ser executado,
chame a API normalmente, sem precisar usar dlopen()
e dlsym()
.
As referências fracas da API não exigem suporte adicional do vinculador dinâmico. para que possam ser usados com qualquer versão do Android.
Como ativar referências fracas da API no build
CMake
Transmita -DANDROID_WEAK_API_DEFS=ON
ao executar o CMake. Se você estiver usando o CMake via
externalNativeBuild
, adicione o seguinte ao seu build.gradle.kts
(ou ao
Equivalente no estilo Groovy se você ainda estiver usando build.gradle
):
android {
// Other config...
defaultConfig {
// Other config...
externalNativeBuild {
cmake {
arguments.add("-DANDROID_WEAK_API_DEFS=ON")
// Other config...
}
}
}
}
ndk-build
Adicione o seguinte ao seu arquivo Application.mk
:
APP_WEAK_API_DEFS := true
Se você ainda não tiver um arquivo Application.mk
, crie-o no mesmo
como o arquivo Android.mk
. Outras mudanças nas
build.gradle.kts
(ou build.gradle
) não são necessários para o ndk-build.
Outros sistemas de build
Se você não estiver usando o CMake ou o ndk-build, consulte a documentação do seu build para verificar se há uma maneira recomendada de ativar esse recurso. Se o build não oferece suporte nativo a essa opção, você pode ativar o recurso passando as seguintes sinalizações na compilação:
-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability
O primeiro configura os cabeçalhos do NDK para permitir referências fracas. A segunda curva o aviso de chamadas de API não seguras em um erro.
Consulte o Guia de mantenedores de sistema de compilação para mais informações.
Chamadas de API protegidas
Esse recurso não faz chamadas seguras para novas APIs em um passe de mágica. A única coisa que é adiar um erro de tempo de carregamento para um erro de tempo de chamada. A vantagem é que você pode proteger essa chamada no tempo de execução e retornar adequadamente, seja usando uma implementação alternativa ou notificar o usuário de que esse recurso do aplicativo não está disponível no dispositivo ou evitando esse caminho de código.
O Clang pode emitir um aviso (unguarded-availability
) quando você fizer uma
chamada para uma API que não está disponível para o minSdkVersion
do app. Se você estiver
usando o ndk-build ou o arquivo do conjunto de ferramentas do CMake, esse aviso será automaticamente
ativado e promovido para um erro ao ativar este recurso.
Aqui está um exemplo de código que faz uso condicional de uma API sem
esse recurso foi ativado, usando dlopen()
e dlsym()
:
void LogImageDecoderResult(int result) {
void* lib = dlopen("libjnigraphics.so", RTLD_LOCAL);
CHECK_NE(lib, nullptr) << "Failed to open libjnigraphics.so: " << dlerror();
auto func = reinterpret_cast<decltype(&AImageDecoder_resultToString)>(
dlsym(lib, "AImageDecoder_resultToString")
);
if (func == nullptr) {
LOG(INFO) << "cannot stringify result: " << result;
} else {
LOG(INFO) << func(result);
}
}
A leitura é um pouco confusa, há alguns nomes de funções duplicados (e, se
estiver escrevendo C, as assinaturas também), ele será criado com êxito, mas sempre
usar o substituto no ambiente de execução se você digitar acidentalmente o nome da função transmitida.
como dlsym
, e você precisa usar esse padrão para cada API.
Com referências fracas da API, a função acima pode ser reescrita como:
void LogImageDecoderResult(int result) {
if (__builtin_available(android 31, *)) {
LOG(INFO) << AImageDecoder_resultToString(result);
} else {
LOG(INFO) << "cannot stringify result: " << result;
}
}
Internamente, o __builtin_available(android 31, *)
chama
android_get_device_api_level()
, armazena o resultado em cache e o compara com 31
(que é o nível da API que introduziu AImageDecoder_resultToString()
).
A maneira mais simples de determinar qual valor usar para __builtin_available
é
tentar construir sem a guarda (ou uma guarda de
__builtin_available(android 1, *)
) e faça o que a mensagem de erro diz.
Por exemplo, uma chamada sem segurança para AImageDecoder_createFromAAsset()
com
minSdkVersion 24
vai produzir:
error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]
Nesse caso, a chamada precisa ser protegida por __builtin_available(android 30, *)
.
Se não houver erro de compilação, significa que a API estará sempre disponível para seu
minSdkVersion
e nenhuma proteção é necessária ou seu build está configurado incorretamente e o
O aviso unguarded-availability
está desativado.
Como alternativa, a referência da API NDK dirá algo ao longo das linhas de "Introduzido na API 30" para cada API. Se esse texto não estiver presente, significa que a API está disponível para todos os níveis de API com suporte.
Evitar a repetição de proteções da API
Se estiver usando isso, você provavelmente terá seções de código em seu aplicativo que
que só podem ser usadas
em dispositivos novos o suficiente. Em vez de repetir
__builtin_available()
em cada função, é possível fazer anotações
que exigem um determinado nível de API. Por exemplo, as APIs ImageDecoder
foram adicionados na API 30, portanto, para funções que fazem uso intenso desses
APIs, você pode fazer algo como:
#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)
void DecodeImageWithImageDecoder() REQUIRES_API(30) {
// Call any APIs that were introduced in API 30 or newer without guards.
}
void DecodeImageFallback() {
// Pay the overhead to call the Java APIs via JNI, or use third-party image
// decoding libraries.
}
void DecodeImage() {
if (API_AT_LEAST(30)) {
DecodeImageWithImageDecoder();
} else {
DecodeImageFallback();
}
}
Características dos guardas de API
O Clang é muito específico sobre como o __builtin_available
é usado. Apenas um literal
(embora possivelmente substituído por macro) if (__builtin_available(...))
funciona. Uniforme
operações triviais como if (!__builtin_available(...))
não vão funcionar (Clang)
emitirá o aviso unsupported-availability-guard
, assim como
unguarded-availability
). Isso pode melhorar em uma versão futura do Clang. Consulte
Consulte o problema 33161 do LLVM (link em inglês) para saber mais.
As verificações de unguarded-availability
só se aplicam ao escopo da função em que
são usadas. O Clang emitirá o aviso mesmo que a função com a chamada de API seja
só pode ser chamado de dentro de um escopo protegido. Para evitar a repetição de guardas
seu próprio código, consulte Como evitar a repetição de proteções de API.
Por que esse não é o padrão?
A menos que usada corretamente, a diferença entre referências fortes e APIs fracas de referência é que a primeira vai falhar rápida e obviamente, enquanto O segundo método não falhará até que o usuário execute uma ação que cause a ausência da API que será chamado. Quando isso acontece, a mensagem de erro não é clara. Tempo de compilação: "AFoo_bar() não está disponível" será uma falha de segmentação. Com referências fortes, a mensagem de erro é muito mais clara e a falha rápido é uma mais seguro.
Como esse é um recurso novo, pouquíssimos códigos são criados para lidar esse comportamento com segurança. Código de terceiros que não foi criado pensando no Android provavelmente sempre terão esse problema. Por isso, não há planos para a comportamento padrão a mudar.
Recomendamos o uso desse recurso, mas, como ele cria mais problemas, difíceis de detectar e depurar, você deve aceitar esses riscos intencionalmente, em vez do que a mudança de comportamento sem seu conhecimento.
Avisos
Esse recurso funciona para a maioria das APIs, mas há alguns casos em que não funcionam.
As APIs da libc mais recentes têm menos probabilidade de serem problemáticas. Ao contrário do restante
APIs do Android, elas são protegidas por #if __ANDROID_API__ >= X
nos cabeçalhos.
e não apenas __INTRODUCED_IN(X)
, o que impede até mesmo a declaração fraca de
ser vistos. Como o suporte a NDKs modernos de nível mais antigo da API é r21, a
as APIs da libc comumente necessárias já estão disponíveis. Novas APIs libc são adicionadas a cada
(consulte status.md), mas quanto mais recentes elas forem, maior será a probabilidade
pode ser um caso extremo que poucos desenvolvedores vão precisar. Dito isso, se você é um dos
com esses desenvolvedores. Por enquanto, você vai precisar continuar usando dlsym()
para chamá-los
as APIs se a minSdkVersion
for mais antiga que a API. Esse é um problema solucionável,
mas isso traz o risco de corromper a compatibilidade de origem de todos os apps (qualquer
código que contém polyfills de APIs libc não será compilada devido ao
atributos availability
incompatíveis nas declarações locais e de libc), então
não temos certeza se ou quando o corrigiremos.
O caso que mais desenvolvedores provavelmente encontrarão é quando a biblioteca que
contém a nova API é mais recente do que seu minSdkVersion
. Somente este recurso
ativa referências fracas de símbolos; não existe uma biblioteca fraca
de referência. Por exemplo, se a minSdkVersion
for 24, você poderá vincular
libvulkan.so
e fazer uma chamada protegida para vkBindBufferMemory2
, porque
O libvulkan.so
está disponível em dispositivos a partir do nível 24 da API. Por outro lado,
se minSdkVersion
era 23, você precisa voltar para dlopen
e dlsym
porque a biblioteca não existe no dispositivo em dispositivos com suporte
API 23. Não sabemos uma boa solução para corrigir este caso, mas, em longo
ele se resolverá sozinho, pois (sempre que possível) não permitimos mais
APIs para criar novas bibliotecas.
Para autores de bibliotecas
Se você estiver desenvolvendo uma biblioteca para ser usada em aplicativos Android, recomendamos
evite usar esse recurso em cabeçalhos públicos. Ele pode ser usado com segurança em
código fora de linha, mas se você depende de __builtin_available
em qualquer código da
cabeçalhos, como funções inline ou definições de modelo, você força todas as
para ativar esse recurso. Pelos mesmos motivos que não ativamos essa
por padrão no NDK, evite fazer essa escolha em nome
de seus clientes.
Se você exigir esse comportamento em seus cabeçalhos públicos, documente para que os usuários saibam que precisam ativar o recurso e cientes dos riscos de fazer isso.