Guida al wrapper libreria

Questa guida descrive come utilizzare il wrapper della libreria API di Android. Lo strumento a riga di comando del wrapping di librerie genera un codice wrapper in linguaggio C per le API Android Java, che ti consente di integrare le librerie Java nelle app Android C/C++ native. Per ulteriori dettagli sul wrapper libreria, consulta la pagina Wrapper libreria per le API Android.

Questa guida passo passo illustra come utilizzare lo strumento wrapper per integrare una libreria Java in un'app Android nativa. Ad esempio, questa guida illustra l'integrazione della libreria di notifica del pacchetto androidx.core.app. Per ulteriori informazioni su questa raccolta, consulta l'articolo Creare una notifica.

Prerequisiti

Questa guida presuppone che tu abbia un progetto Android nativo esistente. Utilizza anche il sistema di build Gradle. Se non hai ancora un progetto, creane uno nuovo in Android Studio utilizzando il modello C++ nativo.

Il codice di esempio in questa guida utilizza la directory radice my_project/. Il codice nativo si trova in my_project/app/src/main/cpp/, la directory predefinita per i progetti Android Studio.

Se non hai ancora lo strumento per il wrapper della libreria, scarica e decomprimi il pacchetto nella directory che preferisci. Questo strumento dell'interfaccia a riga di comando richiede JRE (Java Runtime Environment).

Genera codice nativo

Quando integri una libreria Java, usa lo strumento wrapper per generare un wrapper di codice nativo. Il primo passaggio consiste nel configurare il wrapper.

Crea la configurazione del wrapper

Puoi creare file di configurazione del wrapper libreria per controllare l'output del generatore di codice nativo. Una caratteristica di questo file consente di specificare le classi e i metodi per generare il codice wrapper.

Poiché non esistono molti metodi per eseguire il wrapping della libreria delle notifiche, puoi definirli direttamente nella sezione custom_classes. Crea una nuova risorsa config.json in qualsiasi punto del progetto per definire i metodi. Ad esempio, puoi creare my_project/library_wrapper/config.json e incollare la seguente configurazione di esempio:

{
  "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)"
      ]
    }
  ]
}

Nell'esempio precedente, dichiari direttamente le classi e i metodi Java che richiedono il codice wrapper nativo.

Esegui il wrapper libreria

Una volta definito il file di configurazione del wrapper, puoi utilizzare lo strumento per generare il codice del wrapper nativo. Apri un terminale nel punto in cui hai estratto il wrapper della libreria ed esegui questo comando:

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

Nell'esempio precedente, utilizzi il parametro -c per specificare la posizione della configurazione del wrapper e il parametro -o per definire la directory del codice generato. Dopo aver eseguito lo strumento, dovresti disporre del codice generato necessario per chiamare l'API di notifica basata su Java dalla tua app nativa.

Implementare le notifiche native

In questa sezione integrerai la libreria di notifiche di Android nella tua app nativa utilizzando il codice wrapper generato. Il primo passaggio consiste nell'aggiornare la risorsa gradle.build a livello di app (my_project/app/gradle.build) del progetto.

Aggiorna gradle.build

  1. GNI è una libreria di supporto richiesta dal codice wrapper generato. Tutti i progetti che utilizzano il codice generato devono fare riferimento a questa libreria. Per fare riferimento a questa libreria, aggiungi la seguente riga alla sezione dependencies di build.gradle:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Per attivare il supporto dei prefabbricati, aggiungi il seguente codice alla sezione android:

    buildFeatures {
      prefab true
    }
    
  3. Per configurare cmake, utilizza la seguente configurazione di cmake nella sezione android/defaultConfig:

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

La configurazione di build.gradle completata dovrebbe essere simile alla seguente:

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'
    ...
}

Modifica CMakeLists

  1. Aggiungi la libreria GNI al CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt) del tuo progetto aggiungendo la seguente riga al livello superiore del file:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Aggiungi la seguente riga alla sezione target_link_libraries:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Aggiungi un riferimento al codice generato aggiungendo la seguente riga al livello superiore del file:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Aggiungi queste righe verso la fine del file:

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

La risorsa CMakeLists.txt aggiornata dovrebbe essere simile all'esempio seguente:

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)

Implementa la logica di notifica

  1. Apri o crea il file di origine in cui vuoi implementare le funzionalità di notifica. In questo file, includi il file di intestazione gni.h e definisci una nuova funzione 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. Definisce i valori della costante specifici per le notifiche e le funzioni del gestore delle notifiche CharSequenceFromCString() e CreateNotification():

    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;
    }
    

    Alcune funzioni della libreria delle notifiche utilizzano CharSequence anziché String. La funzione CharSequenceFromCString() consente la conversione tra questi oggetti. La funzione CreateNotification() utilizza la versione con wrapping di Java NotificationCompat.Builder per creare una notifica.

  3. Aggiungi logica per creare un canale di notifica incollando la seguente funzione, 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. Aggiorna la funzione ShowNativeNotification() creata in precedenza per chiamare CreateNotificationChannel(). Aggiungi il seguente codice alla fine di 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. Una volta definita la logica, attiva una notifica chiamando ShowNativeNotification() in una posizione appropriata nel progetto.

Eseguire l'app

Compila ed esegui il codice che chiama ShowNativeNotification(). Dovrebbe essere visualizzata una semplice notifica nella parte superiore dello schermo del dispositivo di test.

Genera wrapper da JAR

Nell'esempio precedente, hai definito manualmente classi e metodi Java che richiedono codice nativo in un file di configurazione wrapper. Per gli scenari in cui è necessario accedere a ampie sezioni di un'API, è più efficiente fornire uno o più JAR di libreria allo strumento wrapper. Il wrapper genera quindi wrapper per tutti i simboli pubblici che trova nel JAR.

L'esempio seguente esegue il wrapping dell'intera API Notifications fornendo un JAR della libreria.

Scarica i JAR richiesti

L'API Notification fa parte del pacchetto androidx.core, disponibile nel repository Google Maven. Scarica il file della libreria aar e decomprimilo in una directory a tua scelta. Individua il file classes.jar.

Il file classes.jar contiene molte classi oltre alla nostra libreria di notifiche obbligatoria. Se fornisci il wrapper libreria solo con classes.jar, lo strumento genera codice nativo per ogni classe nel JAR, il che non è efficace e non è necessario per il nostro progetto. Per risolvere il problema, fornisci un file di filtro alla configurazione del wrapper per limitare la generazione di codice alle classi di notifica JAR.

Definisci un filtro di autorizzazione

I file filtro sono file di testo normale che vengono forniti alla configurazione del wrapper della libreria. Consentono di definire quali classi includere (o escludere) dai file JAR forniti al wrapper della libreria.

Nel tuo progetto, crea un file denominato allowed-symbols.txt e incollalo nella riga seguente:

androidx.core.app.NotificationCompat*

Quando viene utilizzato come filtro di autorizzazione, il codice precedente specifica che vengono aggregati solo i simboli il cui nome inizia con androidx.core.app.NotificationCompat.

Esegui il wrapper libreria

Apri un terminale nella directory JAR ed esegui questo comando:

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

Il comando di esempio precedente genera il codice wrapper per le classi filtrate nella directory generated-jar/.

Assistenza

Se riscontri un problema con il wrapper della libreria, non esitare a comunicarcelo.

Sfoglia bug Segnala un bug
Ingegneria
Documentazione