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
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
debuild.gradle
:implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
Para habilitar la compatibilidad con prefab, agrega el siguiente código a la sección
android
:buildFeatures { prefab true }
Para configurar
cmake
, usa la siguiente configuración decmake
en la secciónandroid/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
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)
Agrega la siguiente línea a la sección
target_link_libraries
:PUBLIC com.google.android.gms.gni.c::gni_shared
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")
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
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ónShowNativeNotification()
:#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); }
Define los valores constantes específicos de notificaciones y las funciones del controlador de notificaciones
CharSequenceFromCString()
yCreateNotification()
: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 deString
. La funciónCharSequenceFromCString()
habilita la conversión entre estos objetos. La funciónCreateNotification()
usa la versión unida deNotificationCompat.Builder
de Java para crear una notificación.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(¬ification_manager_as_object); NotificationManager::destroy(notification_manager); }
Actualiza la función
ShowNativeNotification()
que creaste antes para llamar aCreateNotificationChannel()
. Agrega el siguiente código al final deShowNativeNotification()
: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(¬ification); NotificationManagerCompat::destroy(¬ification_manager); }
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 | bug_report |
Documentación | bug_report |