Guide du wrapper de bibliothèque

Ce guide explique comment utiliser le wrapper de bibliothèque de l'API Android. L'outil de ligne de commande du wrapper de bibliothèque génère un code de wrapper en langage C pour les API Java Android, ce qui vous permet d'intégrer des bibliothèques Java dans des applications Android C/C++ natives. Pour en savoir plus sur le wrapper de bibliothèque, consultez la section Wrapper de bibliothèque pour les API Android.

Ce guide par étapes explique comment utiliser l'outil wrapper pour intégrer une bibliothèque Java à une application Android native. Par exemple, il couvre l'intégration de la bibliothèque de notifications correspondant au package androidx.core.app. Pour en savoir plus sur cette bibliothèque, consultez la section Créer une notification.

Conditions préalables

Ce guide suppose que vous disposez d'un projet Android natif. Il repose également sur le système de compilation Gradle. Si vous ne disposez d'aucun projet, créez-en un dans Android Studio à l'aide du modèle Native C++.

L'exemple de code de ce guide utilise la racine du répertoire my_project/. Le code natif se trouve dans my_project/app/src/main/cpp/, le répertoire par défaut des projets Android Studio.

Si vous ne disposez pas encore de l'outil wrapper de bibliothèque, téléchargez et décompressez le package dans le répertoire de votre choix. Cet outil CLI nécessite l'environnement d'exécution Java (JRE).

Générer du code natif

Lors de l'intégration d'une bibliothèque Java, utilisez l'outil wrapper pour générer un wrapper de code natif. La première étape consiste à configurer le wrapper.

Créer la configuration du wrapper

Créez des fichiers de configuration pour le wrapper de bibliothèque afin de contrôler la sortie du générateur de code natif. Une fonctionnalité de ce fichier vous permet de spécifier les classes et les méthodes pour générer le code wrapper.

Comme il n'y a pas beaucoup de méthodes à encapsuler pour la bibliothèque de notifications, vous pouvez les définir directement dans la section custom_classes. Créez une ressource config.json n'importe où dans le projet pour définir les méthodes. Par exemple, vous pouvez créer my_project/library_wrapper/config.json et coller l'exemple de configuration suivant :

{
  "custom_classes": [
    {
      "class_name": "class java.lang.CharSequence"
    },
    {
      "class_name": "class java.lang.Object",
      "methods": [
        "java.lang.String toString()"
      ]
    },
    {
      "class_name": "class java.lang.String"
    },
    {
      "class_name": "class android.content.Context",
      "methods": [
        "java.lang.Object getSystemService(java.lang.String name)"
      ]
    },
    {
      "class_name": "class android.app.Notification"
    },
    {
      "class_name": "class android.app.NotificationManager",
      "methods": [
        "void createNotificationChannel(android.app.NotificationChannel channel)"
      ]
    },
    {
      "class_name": "class android.app.NotificationChannel",
      "methods": [
        "NotificationChannel(java.lang.String id, java.lang.CharSequence name, int importance)",
        "void setDescription(java.lang.String description)"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat"
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat$Builder",
      "methods": [
        "Builder(android.content.Context context, java.lang.String channelId)",
        "androidx.core.app.NotificationCompat$Builder setContentText(java.lang.CharSequence text)",
        "androidx.core.app.NotificationCompat$Builder setContentTitle(java.lang.CharSequence title)",
        "androidx.core.app.NotificationCompat$Builder setSmallIcon(int icon)",
        "androidx.core.app.NotificationCompat$Builder setPriority(int pri)",
        "android.app.Notification build()"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationManagerCompat",
      "methods": [
        "static androidx.core.app.NotificationManagerCompat from(android.content.Context context)",
        "void notify(int id, android.app.Notification notification)"
      ]
    }
  ]
}

Dans l'exemple précédent, vous devez déclarer directement les classes et méthodes Java qui nécessitent du code de wrapper natif.

Exécuter le wrapper de bibliothèque

Une fois le fichier de configuration du wrapper défini, l'outil est prêt à être utilisé pour générer du code de wrapper natif. Ouvrez un terminal à l'emplacement où vous avez extrait le wrapper de bibliothèque, puis exécutez la commande suivante :

java -jar lw.jar \
  -o "my_project/app/src/main/cpp/native_wrappers" \
  -c "my_project/library_wrapper/config.json"

Dans l'exemple précédent, vous utilisez le paramètre -c pour spécifier l'emplacement de configuration de votre wrapper, et le paramètre -o pour définir le répertoire de code généré. Après avoir exécuté l'outil, vous devriez maintenant disposer du code généré nécessaire pour appeler l'API de notifications Java à partir de votre application native.

Implémenter les notifications natives

Dans cette section, vous allez intégrer la bibliothèque de notifications Android dans votre application native à l'aide du code de wrapper généré. La première étape consiste à mettre à jour la ressource gradle.build (my_project/app/gradle.build) au niveau de l'application de votre projet.

Mettre à jour gradle.build

  1. GNI est une bibliothèque de support requise par le code wrapper généré. Tous les projets utilisant le code généré doivent référencer cette bibliothèque. Pour référencer cette bibliothèque, ajoutez la ligne suivante à la section dependencies de build.gradle :

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Pour activer la prise en charge de la fonctionnalité prefab, ajoutez le code suivant à la section android :

    buildFeatures {
      prefab true
    }
    
  3. Pour configurer cmake, utilisez la configuration cmake suivante dans la section android/defaultConfig :

    externalNativeBuild {
      cmake {
          arguments '-DANDROID_STL=c++_shared'
      }
    }
    

Une fois terminée, votre configuration build.gradle doit se présenter comme suit :

android {
    ...

    buildFeatures {
        prefab true
    }

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_shared'
            }
        }
    }
}

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    ...
}

Modifier CMakeLists

  1. Ajoutez la bibliothèque GNI au fichier CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt) de votre projet en ajoutant la ligne suivante au niveau supérieur du fichier :

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Ajoutez la ligne suivante dans la section target_link_libraries :

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Ajoutez une référence au code généré en ajoutant la ligne suivante en haut du fichier :

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Ajoutez les lignes suivantes à la fin du fichier :

    include_directories(./native_wrappers/c)
    include_directories(./native_wrappers/cpp)
    

Votre ressource CMakeLists.txt mise à jour doit ressembler à l'exemple suivant :

cmake_minimum_required(VERSION 3.18.1)

project("my_project")

file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")

add_library(
        my_project
        SHARED
        native-lib.cpp
        ${native_wrappers}
        )

find_library(
        log-lib
        log)

find_package(com.google.android.gms.gni.c REQUIRED CONFIG)

target_link_libraries(
        my_project
        PUBLIC com.google.android.gms.gni.c::gni_shared
        ${log-lib})

include_directories(./native_wrappers/c)
include_directories(./native_wrappers/cpp)

Implémenter une logique de notification

  1. Ouvrez ou créez le fichier source dans lequel vous souhaitez implémenter les fonctionnalités de notification. Dans ce fichier, incluez le fichier d'en-tête gni.h et définissez une nouvelle fonction ShowNativeNotification() :

    #include "gni/gni.h"
    
    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
      // Get the JavaVM from the JNIEnv.
      JavaVM *java_vm;
      env->GetJavaVM(&java_vm);
    
      // Initialize the GNI runtime. This function needs to be called before any
      // call to the generated code.
      GniCore_init(java_vm, main_activity);
    }
    
  2. Définissez des valeurs constantes spécifiques aux notifications, et les fonctions CharSequenceFromCString() et CreateNotification() du gestionnaire de notifications :

    C

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = GNI_CAST(CharSequence, String, string);
       // Casting creates a new object, so it needs to be destroyed as normal.
       String_destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification *
    CreateNotification(Context *context, String *channel_id,
                       const char *title, const char *content,
                       int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
       NotificationCompat_Builder *notification_builder =
           NotificationCompat_Builder_construct(context, channel_id);
       NotificationCompat_Builder_setContentTitle(notification_builder,
                                                  title_chars);
       NotificationCompat_Builder_setContentText(notification_builder,
                                                 content_chars);
       NotificationCompat_Builder_setSmallIcon(notification_builder, icon_id);
       NotificationCompat_Builder_setPriority(notification_builder,
                                              PRIORITY_MAX);
    
       // Build a notification.
       Notification *notification =
           NotificationCompat_Builder_build(notification_builder);
    
       // Clean up allocated objects.
       NotificationCompat_Builder_destroy(notification_builder);
       CharSequence_destroy(title_chars);
       CharSequence_destroy(content_chars);
    
       return notification;
    }
    

    C++

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = new CharSequence(string->GetImpl());
       // Casting creates a new object, so it needs to be destroyed as normal.
       String::destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification&
    CreateNotification(Context *context, String *channel_id, const char *title,
                       const char *content, int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
    
       NotificationCompat::Builder *notification_builder = new NotificationCompat::Builder(*context, *channel_id);
       notification_builder->setContentTitle(*title_chars);
       notification_builder->setContentText(*content_chars);
       notification_builder->setSmallIcon(icon_id);
       notification_builder->setPriority(PRIORITY_MAX);
    
       // Build a notification.
       Notification& notification = notification_builder->build();
    
       // Clean up allocated objects.
       NotificationCompat::Builder::destroy(notification_builder);
       CharSequence::destroy(title_chars);
       CharSequence::destroy(content_chars);
    
       return notification;
    }
    

    Certaines fonctions de la bibliothèque de notifications utilisent CharSequence au lieu de String. La fonction CharSequenceFromCString() permet de convertir ces objets entre eux. La fonction CreateNotification() utilise la version encapsulée de Java NotificationCompat.Builder pour créer une notification.

  3. Ajoutez une logique permettant de créer un canal de notification en collant la fonction suivante, CreateNotificationChannel() :

    C

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           NotificationChannel_construct(channel_id, channel_name,
                                         IMPORTANCE_HIGH);
       NotificationChannel_setDescription(channel, channel_description);
    
       Object *notification_manager_as_object =
           Context_getSystemService(context, system_service_name);
       NotificationManager *notification_manager =
           GNI_CAST(NotificationManager, Object,
                    notification_manager_as_object);
    
       NotificationManager_createNotificationChannel(notification_manager,
                                                     channel);
    
       CharSequence_destroy(channel_name);
       String_destroy(channel_description);
       String_destroy(system_service_name);
       NotificationChannel_destroy(channel);
       Object_destroy(notification_manager_as_object);
       NotificationManager_destroy(notification_manager);
    }
    

    C++

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           new NotificationChannel(*channel_id, *channel_name, IMPORTANCE_HIGH);
       channel->setDescription(*channel_description);
    
       Object& notification_manager_as_object =
           context->getSystemService(*system_service_name);
       NotificationManager *notification_manager =
           new NotificationManager(notification_manager_as_object.GetImpl());
    
       notification_manager->createNotificationChannel(*channel);
    
       CharSequence::destroy(channel_name);
       String::destroy(channel_description);
       String::destroy(system_service_name);
       NotificationChannel::destroy(channel);
       Object::destroy(&notification_manager_as_object);
       NotificationManager::destroy(notification_manager);
    }
    
  4. Mettez à jour la fonction ShowNativeNotification() que vous avez créée précédemment pour appeler CreateNotificationChannel(). Ajoutez le code suivant à la fin de ShowNativeNotification() :

    C

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
     // ...
    
     // Create a Context object by wrapping an existing JNI reference.
     Context *context = Context_wrapJniReference(main_activity);
    
     // Create a String object.
     String *channel_id = String_fromCString("new_messages");
    
     // Create a notification channel.
     CreateNotificationChannel(context, channel_id);
    
     // Create a notification with a given title, content, and icon.
     Notification *notification =
         CreateNotification(context, channel_id, "My Native Notification",
                            "Hello!", icon_id);
    
     // Create a notification manager and use it to show the notification.
     NotificationManagerCompat *notification_manager =
         NotificationManagerCompat_from(context);
     NotificationManagerCompat_notify(notification_manager, NOTIFICATION_ID,
                                      notification);
    
     // Destroy all objects.
     Context_destroy(context);
     String_destroy(channel_id);
     Notification_destroy(notification);
     NotificationManagerCompat_destroy(notification_manager);
    }
    

    C++

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
       // Get the JavaVM from the JNIEnv.
       JavaVM *java_vm;
       env->GetJavaVM(&java_vm);
    
       // Initialize the GNI runtime. This function needs to be called before any
       // call to the generated code.
       GniCore::Init(java_vm, main_activity);
    
       // Create a Context object by wrapping an existing JNI reference.
       Context *context = new Context(main_activity);
    
       // Create a String object.
       String *channel_id = String_fromCString("new_messages");
    
       // Create a notification channel.
       CreateNotificationChannel(context, channel_id);
    
       // Create a notification with a given title, content, and icon.
       Notification& notification =
           CreateNotification(context, channel_id, "My Native Notification",
                              "Hello!", icon_id);
    
       // Create a notification manager and use it to show the notification.
       NotificationManagerCompat& notification_manager =
           NotificationManagerCompat::from(*context);
       notification_manager.notify(NOTIFICATION_ID, notification);
    
       // Destroy all objects.
       Context::destroy(context);
       String::destroy(channel_id);
       Notification::destroy(&notification);
       NotificationManagerCompat::destroy(&notification_manager);
    }   
  5. Une fois votre logique définie, déclenchez une notification en appelant ShowNativeNotification() à un emplacement approprié dans votre projet.

Exécuter l'application

Compilez et exécutez le code qui appelle ShowNativeNotification(). Une notification simple devrait s'afficher en haut de l'écran de votre appareil de test.

Générer des wrappers à partir de fichiers JAR

Dans l'exemple précédent, vous avez défini manuellement des classes et des méthodes Java nécessitant du code natif dans un fichier de configuration de wrapper. Lorsque vous devez accéder à de vastes sections d'une API, il est plus efficace de fournir un ou plusieurs fichiers JAR de bibliothèque à l'outil de wrapper. Le wrapper génère ainsi des wrappers pour tous les symboles publics qu'il trouve dans le fichier JAR.

L'exemple suivant encapsule l'ensemble de l'API Notifications en fournissant un fichier JAR de bibliothèque.

Obtenir les fichiers JAR requis

L'API Notifications fait partie du package androidx.core, disponible dans le dépôt Google Maven. Téléchargez le fichier aar de la bibliothèque et décompressez-le dans le répertoire de votre choix. Localisez le fichier classes.jar.

Le fichier classes.jar contient de nombreuses classes au-delà de notre bibliothèque de notifications, qui est requise. Si vous fournissez simplement le wrapper de bibliothèque classes.jar, l'outil génère du code natif pour chaque classe dans le fichier JAR, ce qui est contre-productif pour notre projet. Pour résoudre ce problème, fournissez un fichier de filtre à la configuration du wrapper afin de limiter la génération de code aux classes de notification du fichier JAR.

Définir un filtre d'autorisation

Les fichiers de filtre sont des fichiers au format texte brut que vous fournissez à la configuration du wrapper de bibliothèque. Ils vous permettent de définir les classes à inclure (ou à exclure) dans les fichiers JAR fournis au wrapper de bibliothèque.

Dans votre projet, créez un fichier nommé allowed-symbols.txt et collez la ligne suivante :

androidx.core.app.NotificationCompat*

Lorsqu'il est utilisé en tant que filtre d'autorisation, le code précédent spécifie que seuls les symboles dont le nom commence par androidx.core.app.NotificationCompat sont encapsulés.

Exécuter le wrapper de bibliothèque

Ouvrez un terminal dans le répertoire JAR et exécutez la commande suivante :

java -jar lw.jar \
 -i classes.jar \
 -o "./generated-jar" \
 -c "./config.json" \
 -fa allowed-symbols.txt \
 --skip_deprecated_symbols

L'exemple de commande précédent génère un code de wrapper pour les classes que vous avez filtrées dans le répertoire generated-jar/.

Assistance

Si vous rencontrez un problème avec le wrapper de bibliothèque, veuillez nous en informer.

Parcourir les bugs Signaler un bug
Ingénierie
Documentation