Guía del wrapper de bibliotecas

En esta guía, se describe cómo usar el wrapper de bibliotecas de la API de Android. La herramienta de línea de comandos del wrapper de bibliotecas genera código de wrapper de lenguaje C para las APIs de Android para Java, lo que te permite integrar bibliotecas de Java en apps nativas de C/C++ para Android. Si quieres obtener más detalles sobre el wrapper de bibliotecas, consulta Wrapper de bibliotecas para las APIs de Android.

En esta guía paso a paso, se muestra cómo usar la herramienta del wrapper para integrar una biblioteca de Java a una app nativa para Android. A modo de ejemplo, esta guía abarca la integración de la biblioteca de notificaciones del paquete androidx.core.app. Consulta Cómo crear una notificación para obtener más información acerca de esta biblioteca.

Requisitos previos

En esta guía, se da por sentado que tienes un proyecto de Android nativo existente. También se usa el sistema de compilación de Gradle. Si no tienes un proyecto existente, crea uno nuevo en Android Studio con la plantilla Native C++.

En el código de ejemplo de esta guía, se usa la raíz del directorio my_project/. El código nativo se encuentra en my_project/app/src/main/cpp/, el directorio predeterminado para los proyectos de Android Studio.

Si todavía no tienes la herramienta del wrapper de bibliotecas, descarga y descomprime el paquete en el directorio que elijas. Esta herramienta de la CLI requiere Java Runtime Environment (JRE).

Cómo generar código nativo

Cuando integres una biblioteca de Java, usa la herramienta del wrapper para generar un wrapper de código nativo. El primer paso es configurar el wrapper.

Crea la configuración del wrapper

Los archivos de configuración del wrapper de bibliotecas se crean para controlar el resultado del generador de código nativo. Una característica de este archivo te permite especificar las clases y los métodos para generar código de wrapper.

Como no hay muchos métodos para unir para la biblioteca de notificaciones, puedes definirlos directamente en la sección custom_classes. Crea un recurso config.json nuevo en cualquier parte del proyecto para definir los métodos. Por ejemplo, puedes crear my_project/library_wrapper/config.json y pegar la siguiente configuración de ejemplo:

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

En el ejemplo anterior, declaras directamente las clases y los métodos de Java que requieren código de wrapper nativo.

Ejecuta el wrapper de bibliotecas

Con tu archivo de configuración de wrapper definido, está todo listo para usar la herramienta de generación de código de wrapper nativo. Abre una terminal en la que extrajiste el wrapper de bibliotecas y ejecuta el siguiente comando:

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

En el ejemplo anterior, se usa el parámetro -c para especificar la ubicación de la configuración del wrapper y el parámetro -o para definir el directorio del código generado. Después de ejecutar la herramienta, deberías haber generado el código necesario para llamar a la API de notificaciones basadas en Java desde tu app nativa.

Cómo implementar notificaciones nativas

En esta sección, integrarás la biblioteca de notificaciones de Android en tu app nativa usando el código del wrapper generado. El primer paso es actualizar el recurso gradle.build del proyecto (my_project/app/gradle.build) a nivel de la app.

Actualiza gradle.build

  1. GNI es una biblioteca de compatibilidad requerida por el código de wrapper generado. Todos los proyectos que usan código generado deben hacer referencia a esta biblioteca. Para hacer referencia a esta biblioteca, agrega la siguiente línea a la sección dependencies de build.gradle:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Para habilitar la compatibilidad con prefab, agrega el siguiente código a la sección android:

    buildFeatures {
      prefab true
    }
    
  3. Para configurar cmake, usa la siguiente configuración de cmake en la sección android/defaultConfig:

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

La configuración de build.gradle completada debería ser similar a lo siguiente:

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. Para agregar la biblioteca de GNI al CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt) de tu proyecto, agrega la siguiente línea en el nivel superior del archivo:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Agrega la siguiente línea a la sección target_link_libraries:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Agrega la siguiente línea en el nivel superior del archivo para incluir una referencia al código generado:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Agrega estas líneas cerca del final del archivo:

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

Tu recurso CMakeLists.txt actualizado debería parecerse al siguiente ejemplo:

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 lógica de notificación

  1. Abre o crea el archivo de origen en el que deseas implementar la funcionalidad de notificación. En este archivo, incluye el archivo de encabezado gni.h y define una nueva función 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. Define los valores constantes específicos de notificaciones y las funciones del controlador de notificaciones CharSequenceFromCString() y 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;
    }
    

    Algunas funciones de la biblioteca de notificaciones toman CharSequence en lugar de String. La función CharSequenceFromCString() habilita la conversión entre estos objetos. La función CreateNotification() usa la versión unida de NotificationCompat.Builder de Java para crear una notificación.

  3. Agrega lógica para crear un canal de notificaciones pegando la siguiente función, 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. Actualiza la función ShowNativeNotification() que creaste antes para llamar a CreateNotificationChannel(). Agrega el siguiente código al final 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. Con tu lógica definida, activa una notificación con una llamada a ShowNativeNotification() en una ubicación adecuada de tu proyecto.

Ejecuta la app

Compila y ejecuta el código que llama a ShowNativeNotification(). Debería aparecer una notificación simple en la parte superior de la pantalla del dispositivo de prueba.

Cómo generar wrappers a partir de archivos JAR

En el ejemplo anterior, definiste manualmente las clases y los métodos de Java que requerían código nativo en un archivo de configuración de wrapper. Para casos en los que necesitas acceder a secciones grandes de una API, es más eficiente proporcionar uno o más archivos JAR de bibliotecas a la herramienta del wrapper. Luego, el wrapper genera wrappers para todos los símbolos públicos que encuentre en el JAR.

En el siguiente ejemplo, se une toda la API de notificaciones proporcionando una biblioteca JAR.

Obtén los archivos JAR necesarios

La API de notificaciones forma parte del paquete androidx.core, disponible en el repositorio de Google Maven. Descarga el archivo aar de la biblioteca y descomprímelo en el directorio que desees. Ubica el archivo classes.jar.

El archivo classes.jar contiene muchas clases aparte de la biblioteca de notificaciones requerida. Si proporcionas el wrapper de bibliotecas solo con classes.jar, la herramienta generará código nativo para cada clase en el JAR, lo que es ineficiente y no es necesario para nuestro proyecto. Para resolver esto, proporciona un archivo de filtro a la configuración del wrapper para restringir la generación de código a las clases de notificación del JAR.

Define un filtro de permiso

Los archivos de filtro son archivos de texto sin formato que proporcionas a la configuración del wrapper de bibliotecas. Te permiten definir qué clases incluir (o excluir) en los archivos JAR que se proporcionan al wrapper de bibliotecas.

En tu proyecto, crea un archivo llamado allowed-symbols.txt y pega la siguiente línea:

androidx.core.app.NotificationCompat*

Cuando se usa como filtro de permiso, el código anterior especifica que solo se unen los símbolos cuyos nombres comienzan con androidx.core.app.NotificationCompat.

Ejecuta el wrapper de bibliotecas

Abre una terminal en el directorio JAR y ejecuta el siguiente comando:

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

El comando de muestra anterior genera un código de wrapper para tus clases filtradas en el directorio generated-jar/.

Asistencia

Si encuentras un problema con el wrapper de bibliotecas, comunícate con nosotros.

Explora errores Informa un error
Ingeniería
Documentación