This guide describes how to use the Android API library wrapper. The library wrapper command-line tool generates C-language wrapper code for Java Android APIs, enabling you to integrate Java libraries into native C/C++ Android apps. For more details on the library wrapper, see Library wrapper for Android APIs.
This step-by-step guide demonstrates how to use the wrapper tool to integrate a
Java library into a native Android app. For example purposes, this guide covers
integrating the notification library of the androidx.core.app
package.
See Create a Notification to learn more about this library.
Prerequisites
This guide assumes you have an existing native Android project. It also uses the Gradle build system. If you don't have an existing project, create a new one in Android Studio using the Native C++ template.
The example code in this guide uses the directory root my_project/
. Native
code is located in my_project/app/src/main/cpp/
, the default directory for
Android Studio projects.
If you don't already have the library wrapper tool, download and unzip the package to the directory of your choice. This CLI tool requires the Java Runtime Environment (JRE).
Generate native code
When integrating a Java library, use the wrapper tool to generate a native code wrapper. The first step is to configure the wrapper.
Create the wrapper config
You create library wrapper configuration files to control the output of the native code generator. One feature of this file lets you specify the classes and methods to generate wrapper code.
Since there aren't many methods to wrap for the notifications library, you can
define them directly in the custom_classes
section. Create a new
config.json
resource anywhere in your project to define the methods. For example,
you can create my_project/library_wrapper/config.json
and paste the following
sample configuration:
{
"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)"
]
}
]
}
In the preceding sample, you directly declare the Java classes and methods that require native wrapper code.
Run the library wrapper
With your wrapper config file defined, you're ready to use the tool to generate native wrapper code. Open a terminal to where you extracted the library wrapper and run the following command:
java -jar lw.jar \
-o "my_project/app/src/main/cpp/native_wrappers" \
-c "my_project/library_wrapper/config.json"
In the preceding sample you use the -c
parameter to specify your wrapper
config location, and the -o
parameter to define the generated code directory.
After running the tool, you should now have the generated code needed to call the
Java-based notifications API from your native app.
Implement native notifications
In this section, you integrate the Android notifications library into your
native app using your generated wrapper code. The first step is to update your
project's app-level gradle.build
resource (my_project/app/gradle.build
).
Update gradle.build
GNI is a support library required by the generated wrapper code. All projects using generated code should reference this library. To reference this library, add the following line to the
dependencies
section ofbuild.gradle
:implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
To enable prefab support, add the following code to the
android
section:buildFeatures { prefab true }
To configure
cmake
, use the followingcmake
configuration in theandroid/defaultConfig
section:externalNativeBuild { cmake { arguments '-DANDROID_STL=c++_shared' } }
Your completed build.gradle
configuration should resemble the following:
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'
...
}
Modify CMakeLists
Add the GNI library to your project's
CMakeLists.txt
(my_project/app/src/main/cpp/CMakeLists.txt
) by adding the following line at the top level of the file:find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
Add the following line to the
target_link_libraries
section:PUBLIC com.google.android.gms.gni.c::gni_shared
Add a reference to the generated code by adding the following line at the top level of the file:
file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
Add these lines near the end of the file:
include_directories(./native_wrappers/c) include_directories(./native_wrappers/cpp)
Your updated CMakeLists.txt
resource should resemble the following sample:
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)
Implement notification logic
Open or create the source file where you would like to implement notification capabilities. In this file, include the header file
gni.h
and define a newShowNativeNotification()
function:#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 notification-specific constant values, and the notification handler functions
CharSequenceFromCString()
andCreateNotification()
: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; }
Some functions of the notifications library take the
CharSequence
instead ofString
. TheCharSequenceFromCString()
function enables conversion between these objects. The functionCreateNotification()
uses the wrapped version of JavaNotificationCompat.Builder
to create a notification.Add logic to create a notification channel by pasting in the following function,
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); }
Update the
ShowNativeNotification()
function you created earlier to callCreateNotificationChannel()
. Add the following code to the end ofShowNativeNotification()
: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); }
With your logic defined, trigger a notification by calling
ShowNativeNotification()
at an appropriate location in your project.
Run the app
Compile and run the code that calls ShowNativeNotification()
. A simple
notification should appear at the top of the screen of your test device.
Generate wrappers from JARs
In the previous example, you manually defined Java classes and methods requiring native code in a wrapper config file. For scenarios where you need to access large sections of an API, its more efficient to provide one or more library JARs to the wrapper tool. The wrapper then generates wrappers for all public symbols it finds in the JAR.
The following example wraps the entire Notifications API by providing a library JAR.
Get the required JARs
The Notification API is a part of the androidx.core
package, available from
the Google Maven repository. Download the library aar file and unpack it to
a directory of your choice. Locate the classes.jar
file.
The classes.jar
file contains many classes beyond our required notifications
library. If you provide the library wrapper with just classes.jar
, the tool
generates native code for every class in the JAR, which is inefficient and
unnecessary for our project. To solve this, provide a filter file to the
wrapper configuration to restrict code generation to the JAR's notification
classes.
Define an allow filter
Filter files are plain text files you provide to your library wrapper configuration. They allow you to define which classes to include (or exclude) from JAR files provided to the library wrapper.
In your project, create a file titled allowed-symbols.txt
and paste in the
following line:
androidx.core.app.NotificationCompat*
When used as an allow filter, the preceding code specifies that only symbols
whose name starts with androidx.core.app.NotificationCompat
are wrapped.
Run the library wrapper
Open a terminal to the JAR directory and run the following command:
java -jar lw.jar \
-i classes.jar \
-o "./generated-jar" \
-c "./config.json" \
-fa allowed-symbols.txt \
--skip_deprecated_symbols
The preceding sample command generates wrapper code for your filtered classes
to the directory generated-jar/
.
Support
If you find an issue with the library wrapper, please let us know.
Browse bugs | File a bug |
---|---|
Engineering | bug_report |
Documentation | bug_report |