Conseils pour les fournisseurs de middleware

La distribution d'un middleware créé avec le NDK implique quelques particularités supplémentaires dont les développeurs d'applications n'ont pas à se préoccuper. Les bibliothèques prédéfinies imposent une partie de leurs choix d'implémentation à leurs utilisateurs.

Choisir les niveaux d'API et les versions de NDK

Vos utilisateurs ne peuvent pas utiliser une version minSdkVersion inférieure à la vôtre. Si les applications de vos utilisateurs doivent s'exécuter sur l'API 21, votre compilation ne peut pas être destinée à l'API 24. Vous pouvez créer une bibliothèque pour un niveau d'API inférieur à celui de vos utilisateurs. Par exemple, vous pouvez effectuer la compilation pour l'API 16 et rester compatible avec les utilisateurs de l'API 21.

Les versions de NDK sont largement compatibles entre elles, mais il arrive que des modifications compromettent leur compatibilité. Si vous savez que tous vos utilisateurs utilisent la même version du NDK, il est préférable d'utiliser cette version. Sinon, utilisez la version la plus récente.

Utiliser la STL

Si vous écrivez en C++ et que utilisez la STL, le choix entre libc++_shared et libc++_static affecte les utilisateurs si vous distribuez une bibliothèque partagée. Dans ce cas, vous devez utiliser libc++_shared ou vous assurer que les symboles de libc++ ne sont pas exposés par votre bibliothèque. Pour ce faire, le meilleur moyen consiste à déclarer explicitement votre surface d'ABI avec un script de version (cela contribue également à préserver la confidentialité des détails de votre implémentation). Par exemple, une bibliothèque arithmétique simple peut avoir le script de version suivant :

LIBMYMATH {
global:
    add;
    sub;
    mul;
    div;
    # C++ symbols in an extern block will be mangled automatically. See
    # https://stackoverflow.com/a/21845178/632035 for more examples.
    extern "C++" {
        "pow(int, int)";
    }
local:
    *;
};

Nous vous recommandons d'utiliser un script de version, car il s'agit du moyen le plus efficace de contrôler la visibilité des symboles. Il s'agit d'une bonne pratique pour toutes les bibliothèques partagées, qu'il soit question de middlewares ou autres, car elle empêche l'exposition des détails de votre mise en œuvre et réduit le temps de chargement.

Une autre option moins fiable consiste à utiliser -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a lors de l'association. Cette option est moins robuste, car elle ne masque les symboles que dans les bibliothèques explicitement nommées, et qu'aucun diagnostic ne permet d'identifier les bibliothèques non utilisées (une faute d'orthographe dans le nom de la bibliothèque n'est pas considérée comme une erreur, et la mise à jour de la liste des bibliothèques revient à l'utilisateur). Cette approche ne masque pas non plus vos propres détails d'implémentation.

Distribuer des bibliothèques natives dans des AAR

Le plug-in Android Gradle permet d'importer des dépendances natives distribuées dans des AAR. Si vos utilisateurs se servent du plug-in Android Gradle, il s'agit du moyen le plus simple pour eux d'utiliser votre bibliothèque.

Les bibliothèques natives peuvent être empaquetées dans un AAR par AGP. Il s'agit de l'option la plus simple si votre bibliothèque est déjà compilée par externalNativeBuild.

Les builds qui ne relèvent pas d'AGP peuvent utiliser ndkports ou effectuer un empaquetage manuel en suivant la documentation Prefab pour créer le sous-répertoire prefab/ de leur AAR.

Middleware Java avec des bibliothèques JNI

Les bibliothèques Java qui incluent des bibliothèques JNI (en d'autres termes, des fichiers AAR contenant jniLibs) doivent veiller à ce que les bibliothèques JNI incluses ne soient pas en conflit avec d'autres bibliothèques de l'application de l'utilisateur. Par exemple, si l'AAR inclut libc++_shared.so, mais qu'une version de libc++_shared.so différente de celle de l'application est installée, une seule sera installée dans le package APK, ce qui peut entraîner un comportement non fiable.

La solution la plus fiable consiste à ce que les bibliothèques Java n'incluent pas plus d'une bibliothèque JNI (ce qui est également un bon conseil pour les applications). Toutes les dépendances, y compris la STL, doivent être associées à la bibliothèque d'implémentation de manière statique, et un script de version doit être utilisé pour appliquer la surface d'ABI. Par exemple, une bibliothèque Java com.example.foo incluant la bibliothèque JNI libfooimpl.so devrait utiliser le script de version suivant :

LIBFOOIMPL {
global:
    JNI_OnLoad;
local:
    *;
};

Cet exemple utilise registerNatives via JNI_OnLoad comme décrit dans les conseils JNI pour s'assurer que la surface d'ABI minimale est exposée et que le temps de chargement de la bibliothèque est réduit.