Utiliser des API plus récentes

Cette page explique comment votre application peut utiliser les nouvelles fonctionnalités du système d'exploitation lorsqu'elle s'exécute sur un versions d'OS tout en préservant la compatibilité avec les appareils plus anciens.

Par défaut, les références aux API du NDK dans votre application sont des références fortes. Le Dynamic Loader d'Android les résoudra avec impatience lorsque votre bibliothèque sera chargé. Si les symboles sont introuvables, l'application est abandonnée. Cela est contraire à le comportement de Java. Aucune exception ne sera levée tant que l'API manquante n'aura pas été appelé.

Pour cette raison, le NDK vous empêche de créer des références fortes Les API plus récentes que la version minSdkVersion de votre application. Cela vous protège contre en envoyant accidentellement du code qui a fonctionné pendant votre test, mais dont le chargement échouera (UnsatisfiedLinkError sera générée depuis System.loadLibrary()) sur les versions plus anciennes appareils. D'autre part, il est plus difficile d'écrire du code qui utilise des API plus récent que le minSdkVersion de votre application, car vous devez appeler les API à l'aide de dlopen() et dlsym() plutôt qu'un appel de fonction normal.

L'alternative à l'utilisation de références fortes consiste à utiliser des références faibles. Une faible de référence introuvable lors du chargement de la bibliothèque renvoie l'adresse de ce symbole doit être défini sur nullptr au lieu d'échouer à charger. Ils continuent ne peut pas être appelé en toute sécurité, mais tant que les sites d'appel sont protégés pour empêcher les appels l'API lorsqu'elle n'est pas disponible, le reste de votre code peut être exécuté, et vous pouvez appeler l'API normalement, sans avoir à utiliser dlopen() et dlsym().

Les références d'API faibles ne nécessitent pas de soutien supplémentaire de la part de l'éditeur de liens dynamiques. afin de pouvoir être utilisés avec n'importe quelle version d'Android.

Activer les références d'API faibles dans votre build

CMake

Transmettez -DANDROID_WEAK_API_DEFS=ON lors de l'exécution de CMake. Si vous utilisez CMake externalNativeBuild, ajoutez ce qui suit à votre build.gradle.kts (ou à Équivalent Groovy si vous utilisez toujours build.gradle):

android {
    // Other config...

    defaultConfig {
        // Other config...

        externalNativeBuild {
            cmake {
                arguments.add("-DANDROID_WEAK_API_DEFS=ON")
                // Other config...
            }
        }
    }
}

ndk-build

Ajoutez le code ci-dessous à votre fichier Application.mk :

APP_WEAK_API_DEFS := true

Si vous n'avez pas encore de fichier Application.mk, créez-le dans le même comme votre fichier Android.mk. Autres modifications apportées à votre les fichiers build.gradle.kts (ou build.gradle) ne sont pas nécessaires pour ndk-build.

Autres systèmes de compilation

Si vous n'utilisez ni CMake, ni ndk-build, consultez la documentation de votre build. pour voir s'il existe une méthode recommandée pour activer cette fonctionnalité. Si votre build n'est pas compatible avec cette option en natif, vous pouvez l'activer en transmettant les indicateurs suivants lors de la compilation:

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

Le premier configure les en-têtes du NDK pour autoriser les références faibles. Au deuxième tour l'avertissement en cas d'appels d'API non sécurisés en erreur.

Pour en savoir plus, consultez le document Build System Managingers Guide (Créer le guide de gestion du système).

Appels d'API protégés

Cette fonctionnalité ne sécurise pas automatiquement les appels de nouvelles API. La seule chose qu'il reporte une erreur de temps de chargement à une erreur de temps d'appel. L'avantage est que vous peut protéger cet appel au moment de l'exécution et revenir en douceur, que ce soit en utilisant une implémentation alternative ou avertir l'utilisateur que cette fonctionnalité de l'application n'est pas disponible sur leur appareil, ou d'éviter complètement ce chemin de code.

Clang peut émettre un avertissement (unguarded-availability) lorsque vous effectuez une surveillance à une API qui n'est pas disponible pour le minSdkVersion de votre application. Si vous utilisez à l'aide de ndk-build ou de notre fichier de chaîne d'outils CMake, cet avertissement s'affiche automatiquement activée et signalée comme erreur lors de l'activation de cette fonctionnalité.

Voici un exemple de code permettant une utilisation conditionnelle d'une API sans cette fonctionnalité activée, à l'aide de dlopen() et de 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);
    }
}

C'est un peu désordonné à lire, il y a des doublons des noms de fonctions (et si vous écrivez en C et les signatures). le remplacement au moment de l'exécution si vous avez mal orthographié le nom de la fonction transmis à dlsym. Vous devez utiliser ce modèle pour chaque API.

Avec des références d'API faibles, la fonction ci-dessus peut être réécrite comme suit:

void LogImageDecoderResult(int result) {
    if (__builtin_available(android 31, *)) {
        LOG(INFO) << AImageDecoder_resultToString(result);
    } else {
        LOG(INFO) << "cannot stringify result: " << result;
    }
}

En arrière-plan, __builtin_available(android 31, *) appelle android_get_device_api_level(), met en cache le résultat et le compare à 31. (qui est le niveau d'API ayant introduit AImageDecoder_resultToString()).

Le moyen le plus simple de déterminer la valeur à utiliser pour __builtin_available est de de construire sans la garde (ou la garde __builtin_available(android 1, *)) et suivez les instructions indiquées dans le message d'erreur. Par exemple, un appel non surveillé à AImageDecoder_createFromAAsset() avec minSdkVersion 24 produira:

error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]

Dans ce cas, l'appel doit être protégé par __builtin_available(android 30, *). Si aucune erreur de compilation ne se produit, l'API est toujours disponible pour votre minSdkVersion et aucun dispositif de protection n'est nécessaire, ou votre build est mal configuré et le L'avertissement unguarded-availability est désactivé.

La documentation de référence de l'API du NDK indique également "Introduit dans l'API 30" pour chaque API. Si ce texte n'est pas présent, cela signifie que l'API est disponible pour tous les niveaux d'API compatibles.

Éviter la répétition des protections des API

Si vous l'utilisez, il y a probablement des sections de code dans votre application ne sont utilisables que sur des appareils suffisamment récents. Plutôt que de répéter __builtin_available() dans chacune de vos fonctions, vous pouvez annoter vos votre propre code comme nécessitant un certain niveau d'API. Par exemple, les API ImageDecoder elles-mêmes ont été ajoutées à l'API 30. Ainsi, pour les fonctions qui les utilisent de manière intensive, vous pouvez effectuer les opérations suivantes:

#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();
    }
}

Quirks des API Guards

Clang est très précis dans l'utilisation de __builtin_available. Un littéral (bien qu'elle ait été macro-remplacée) if (__builtin_available(...)) fonctionne. Régulière des opérations simples comme if (!__builtin_available(...)) ne fonctionneront pas (Clang émettra l'avertissement unsupported-availability-guard, ainsi que unguarded-availability). Cela pourrait s'améliorer dans une prochaine version de Clang. Voir Problème 33161 de LLVM pour plus d'informations

Les vérifications pour unguarded-availability ne s'appliquent qu'au champ d'application de la fonction où elles sont utilisés. Clang émettra cet avertissement même si la fonction avec l'appel d'API est n'est appelé que depuis un champ d'application protégé. Pour éviter de répéter les garde-fous dans votre propre code, consultez la section Éviter la répétition des protections d'API.

Pourquoi n'est-ce pas la valeur par défaut ?

À moins d'être utilisées correctement, la différence entre les références d'API fortes et les API faibles est que le premier échouera rapidement et de toute évidence, alors que La seconde n'échouera que lorsque l'utilisateur aura effectué une action qui entraînera l'absence d'API à appeler. Dans ce cas, le message d'erreur n'est pas clair au moment de la compilation "AFoo_bar() is not available" (AFoo_bar() n'est pas disponible) il s'agira d'une erreur de segmentation. Avec des références fortes, le message d'erreur est bien plus clair et l'échec rapide plus sécurisé par défaut.

Comme il s'agit d'une nouvelle fonctionnalité, très peu de code existant est écrit pour gérer ce comportement en toute sécurité. Code tiers n'ayant pas été conçu pour Android rencontrera probablement ce problème. Nous n'avons donc pas prévu le comportement par défaut n'est jamais modifié.

Nous vous recommandons effectivement d'utiliser cette méthode, mais elle posera davantage de problèmes. difficiles à détecter et à déboguer, vous devez accepter ces risques en toute connaissance de cause que le comportement qui change à votre insu.

Mises en garde

Cette fonctionnalité est compatible avec la plupart des API, mais, dans certains cas, elle ne peut pas travail.

Les API libc plus récentes sont les moins susceptibles de poser problème. Contrairement au reste du API Android, celles-ci sont protégées par #if __ANDROID_API__ >= X dans les en-têtes et pas seulement __INTRODUCED_IN(X), ce qui empêche même la déclaration faible pour être vus. Étant donné que le niveau d'API le plus ancien compatible avec les NDK modernes est r21, les API libc couramment nécessaires sont déjà disponibles. De nouvelles API libc sont ajoutées à chaque (voir status.md), mais plus elles sont récentes, plus elles sont susceptibles un cas limite dont peu de développeurs auront besoin. Cela dit, si vous faites partie des pour ces développeurs. Pour l'instant, vous devez continuer à utiliser dlsym() pour les appeler API si votre minSdkVersion est antérieur à l'API. Ce problème peut être résolu, Toutefois, cela risque de rompre la compatibilité des sources pour toutes les applications la compilation du code contenant des polyfills d'API libc échouera les attributs availability non concordants sur les déclarations libc et locales). nous ne savons pas si ni quand nous le réglerons.

Les développeurs sont susceptibles de rencontrer davantage de développeurs lorsque la bibliothèque qui contient la nouvelle API est plus récent que votre minSdkVersion. Cette fonctionnalité uniquement active les références aux symboles faibles ; il n’y a pas de bibliothèque faible référence. Par exemple, si votre minSdkVersion a 24 ans, vous pouvez associer libvulkan.so et effectuer un appel protégé à vkBindBufferMemory2, car libvulkan.so est disponible sur les appareils à partir du niveau d'API 24. Par ailleurs, si votre minSdkVersion était de 23, vous devez revenir à dlopen et dlsym car la bibliothèque n'existe pas sur l'appareil pour les appareils qui ne prennent en charge API 23. Nous ne connaissons aucune solution efficace pour résoudre ce problème, mais sachez elle se résout de lui-même, car nous n'autorisons plus (si possible) les nouvelles API permettant de créer des bibliothèques

Pour les auteurs de bibliothèques

Si vous développez une bibliothèque à utiliser dans des applications Android, vous devez évitez d'utiliser cette fonction dans vos en-têtes publics. Il peut être utilisé en toute sécurité dans du code hors ligne, mais si vous utilisez __builtin_available dans le code de votre comme des fonctions intégrées ou des définitions de modèle, vous forcez l'emploi aux consommateurs d'activer cette fonctionnalité. Pour les mêmes raisons, nous n'activons pas fonctionnalité par défaut dans le NDK, vous devez éviter de faire ce choix à la place de vos consommateurs.

Si vous avez besoin de ce comportement dans vos en-têtes publics, assurez-vous de documenter afin que vos utilisateurs sachent qu'ils devront activer la fonctionnalité des risques encourus.