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.