Usa APIs más recientes

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.