En esta página, se explica cómo tu app puede usar la nueva funcionalidad del SO cuando se ejecuta en nuevas versiones de SO y, al mismo tiempo, preserva la compatibilidad con dispositivos anteriores.
De forma predeterminada, las referencias a las APIs del NDK de tu aplicación son referencias sólidas. El cargador dinámico de Android los resolverá cuando tu biblioteca se cargado. Si no se encuentran los símbolos, se anulará la app. Esto es contrario a cómo se comporta Java, donde no se arrojará una excepción hasta que se cumpla la API faltante llamado.
Por esta razón, el NDK te impedirá crear referencias sólidas a
APIs más recientes que la minSdkVersion
de tu app. Esto te protege de
enviar accidentalmente un código que funcionó durante la prueba, pero que no se cargará
(Se arrojará UnsatisfiedLinkError
desde System.loadLibrary()
) en versiones anteriores
dispositivos. Por otro lado, es más difícil escribir código que use APIs
más reciente que la minSdkVersion
de tu app, ya que debes llamar a las APIs usando
dlopen()
y dlsym()
en lugar de una llamada a función normal.
La alternativa al uso de referencias fuertes es usar referencias débiles. Un punto débil
que no se encuentra cuando la biblioteca cargada genera como resultado la dirección de
ese símbolo se establezca en nullptr
, en lugar de que no se cargue. Todavía
no se pueden llamar de forma segura, pero siempre y cuando los sitios de llamadas estén protegidos para evitar las llamadas
la API cuando no está disponible, puedes ejecutar el resto de tu código, y puedes
llamar a la API normalmente sin necesidad de usar dlopen()
y dlsym()
.
Las referencias de API débiles no requieren compatibilidad adicional por parte del vinculador dinámico. para que se puedan usar con cualquier versión de Android.
Cómo habilitar referencias de la API poco seguras en tu compilación
CMake
Pasa -DANDROID_WEAK_API_DEFS=ON
cuando ejecutes CMake. Si usas CMake mediante
externalNativeBuild
, agrega lo siguiente a tu build.gradle.kts
(o
Equivalente de Groovy si aún usas build.gradle
):
android {
// Other config...
defaultConfig {
// Other config...
externalNativeBuild {
cmake {
arguments.add("-DANDROID_WEAK_API_DEFS=ON")
// Other config...
}
}
}
}
ndk-build
Agrega lo siguiente a tu archivo Application.mk
:
APP_WEAK_API_DEFS := true
Si aún no tienes un archivo Application.mk
, créalo en el mismo
como el archivo Android.mk
. Cambios adicionales en su
Los archivos build.gradle.kts
(o build.gradle
) no son necesarios para ndk-build.
Otros sistemas de compilaciones
Si no usas CMake o ndk-build, consulta la documentación de tu compilación. del sistema para ver si existe una forma recomendada de habilitar esta función. Si tu compilación no es compatible con esta opción de forma nativa, puedes habilitar la función y pasa las siguientes marcas durante la compilación:
-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability
El primero configura los encabezados del NDK para permitir referencias débiles. El segundo turno la advertencia de llamadas a la API no seguras en un error.
Consulta la Guía para encargados de mantener sistemas de compilación a fin de obtener más información.
Llamadas a la API protegidas
Esta función no hace que las llamadas a APIs nuevas sean seguras. Lo único que es diferir un error de tiempo de carga a uno de llamada. La ventaja es que puedes proteger esa llamada en el tiempo de ejecución y volver a la normalidad, ya sea usando implementación alternativa o notificar al usuario que esa función de la aplicación se que no están disponibles en su dispositivo, ni tampoco evitar por completo esa ruta de código.
Clang puede emitir una advertencia (unguarded-availability
) cuando actives una alerta
llamada a una API que no está disponible para minSdkVersion
de tu app. Si estás
usando ndk-build o nuestro archivo de la cadena de herramientas de CMake, esa advertencia se mostrará
habilitar y mostrar un error al habilitar esta función.
Este es un ejemplo de un código que hace el uso condicional de una API sin
habilitar esta función con dlopen()
y 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);
}
}
Es un poco desordenado de leer, hay algunos nombres de funciones duplicados (y si
escribes C, las firmas), se compilará con éxito, pero siempre
toma el resguardo en el tiempo de ejecución si cometes accidentalmente un error tipográfico en el nombre de la función que se pasó
a dlsym
, y debes usar este patrón para cada API.
Con referencias de API débiles, la función anterior se puede reescribir de la siguiente manera:
void LogImageDecoderResult(int result) {
if (__builtin_available(android 31, *)) {
LOG(INFO) << AImageDecoder_resultToString(result);
} else {
LOG(INFO) << "cannot stringify result: " << result;
}
}
De forma interna, __builtin_available(android 31, *)
llama
android_get_device_api_level()
, almacena en caché el resultado y lo compara con 31
(que es el nivel de API que introdujo AImageDecoder_resultToString()
).
La forma más sencilla de determinar qué valor usar para __builtin_available
es
de compilar sin la guardia (o un guardia de
__builtin_available(android 1, *)
) y haz lo que te indica el mensaje de error.
Por ejemplo, una llamada sin supervisión a AImageDecoder_createFromAAsset()
con
minSdkVersion 24
producirá lo siguiente:
error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]
En este caso, __builtin_available(android 30, *)
debe proteger la llamada.
Si no aparece ningún error de compilación, la API siempre estará disponible para tu
minSdkVersion
y no se necesita protección, o la compilación está mal configurada y la
La advertencia unguarded-availability
está inhabilitada.
De manera alternativa, la referencia de la API del NDK dirá algo como "Se introdujo en el nivel de API 30" para cada API. Si ese texto no está presente, significa que la API está disponible para todos los niveles de API admitidos.
Cómo evitar la repetición de protecciones de API
Si estás usando esto, probablemente
tengas secciones de código en tu app
solo se pueden usar en dispositivos lo suficientemente nuevos. En lugar de repetir
__builtin_available()
revisa cada una de tus funciones, puedes anotar tus
propio código como requerir un cierto nivel de API. Por ejemplo, las APIs de ImageDecoder
se agregaron en el nivel de API 30, así que, para las funciones que usan mucho esas funciones,
APIs, puedes hacer algo como lo siguiente:
#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();
}
}
Diferencias de los guardias de API
Clang es muy particular con el uso de __builtin_available
. Solo un literal
(aunque posiblemente se reemplazó por macro) if (__builtin_available(...))
funciona. Uniforme
Las operaciones triviales, como if (!__builtin_available(...))
, no funcionarán (Clang
emitirá la advertencia unsupported-availability-guard
y también
unguarded-availability
). Esto podría mejorar en una versión futura de Clang. Consulta
Error 33161 de LLVM para obtener más información.
Las verificaciones de unguarded-availability
solo se aplican al alcance de la función en el que
se usan las reglas de firewall. Clang emitirá la advertencia incluso si la función con la llamada a la API se
solo se llama desde un alcance protegido. Para evitar la repetición de guardias en
tu propio código, consulta Cómo evitar la repetición de protecciones de API.
¿Por qué no es la opción predeterminada?
A menos que se use correctamente, la diferencia entre las referencias de API fuertes y las API débiles referencias es que el primero fallará rápida y obviamente, mientras que Esta última no fallará hasta que el usuario realice una acción que provoque que la API faltante a los que se los debe llamar. Cuando esto sucede, el mensaje de error no será claro tiempo de compilación que “AFoo_bar() no está disponible” será un error de segmento. Con referencias sólidas, el mensaje de error es mucho más claro, y fallar rápido es seguridad predeterminada.
Como es una función nueva, se escribe muy poco código existente para manejar este comportamiento de forma segura. Código de terceros que no se escribió pensando en Android probablemente siempre tenga este problema, por lo que actualmente no hay planes para el comportamiento predeterminado que siempre cambie.
Te recomendamos que la uses, pero tal vez tengas más problemas difíciles de detectar y depurar, debes aceptar esos riesgos a sabiendas en lugar que el cambio de comportamiento sin que lo sepas.
Advertencias
Esta función funciona para la mayoría de las APIs, pero hay algunos casos en los que no el trabajo.
La menos probable que haya problemas son las APIs de libc más nuevas. A diferencia del resto de los
APIs de Android, que están protegidas con #if __ANDROID_API__ >= X
en los encabezados.
y no solo __INTRODUCED_IN(X)
, lo que evita que incluso la declaración débil
que se están viendo. Dado que el nivel de API más antiguo que admiten NDK modernos es r21, la
ya están disponibles las APIs de libc comúnmente necesarias. Se agregan nuevas APIs de libc
(consulta status.md), pero cuanto más recientes sean, más probable es que
podría representar un caso límite
que pocos desarrolladores necesitarán. Dicho esto, si eres uno de
esos desarrolladores, por ahora deberás seguir usando dlsym()
para llamarlos
APIs si tu minSdkVersion
es anterior a la API. Este es un problema que se puede resolver,
pero hacerlo conlleva el riesgo de romper la compatibilidad con el código fuente para todas las apps (cualquier
que contiene polyfills de las APIs de libc no se compilarán debido a las
atributos availability
que no coinciden en las declaraciones libc y locales), por lo que
y no sabemos si lo solucionaremos.
Lo más probable es que los desarrolladores se encuentren con la biblioteca que
contiene la nueva API es más reciente que tu minSdkVersion
. Solo esta función
permite referencias de símbolos débiles; no existe una biblioteca débil
referencia. Por ejemplo, si tu minSdkVersion
es 24, puedes vincular
libvulkan.so
y hacer una llamada protegida a vkBindBufferMemory2
porque
libvulkan.so
está disponible en dispositivos que comienzan con el nivel de API 24. Por otro lado,
si tu minSdkVersion
fuera de 23, debes recurrir a dlopen
y dlsym
porque la biblioteca no existirá en el dispositivo en dispositivos que solo admiten
API 23. No conocemos una buena solución para resolver este caso, pero
a término, se resolverá sola porque (cuando sea posible) ya no permitimos nuevos
APIs para crear bibliotecas nuevas.
Para autores de biblioteca
Si estás desarrollando una biblioteca para usar en aplicaciones de Android, deberías
evita usar esta función en tus encabezados públicos. Se puede utilizar de forma segura en
código fuera de línea, pero si confías en __builtin_available
en cualquier código de tu
como las funciones intercaladas o las definiciones de plantillas, debes forzar
que los consumidores habiliten esta función. Por los mismos motivos, no habilitamos esta
de forma predeterminada en el NDK, debes evitar tomar esa decisión por
de tus consumidores.
Si requieres este comportamiento en tus encabezados públicos, asegúrate de documentar para que los usuarios sepan que deben habilitar la función al tanto de los riesgos que implica hacerlo.