本指南介绍了如何使用 Android API 库封装容器。库封装容器命令行工具可为 Java Android API 生成 C 语言封装容器代码,让您将 Java 库集成到原生 C/C++ Android 应用中。如需详细了解库封装容器,请参阅适用于 Android API 的库封装容器。
本分步指南演示了如何使用封装容器工具将 Java 库集成到原生 Android 应用中。作为示例,本指南介绍了如何集成 androidx.core.app
软件包的通知库。如需详细了解此库,请参阅创建通知。
前提条件
本指南假定您已有一个原生 Android 项目,而且该项目使用 Gradle 构建系统。如果您没有任何现有项目,请在 Android Studio 中使用原生 C++ 模板创建一个新项目。
本指南中示例代码使用的目录的根目录为 my_project/
。原生代码位于 my_project/app/src/main/cpp/
,即 Android Studio 项目的默认目录。
如果您还没有库封装容器工具,请下载软件包并将其解压缩到您选择的目录。此 CLI 工具需要 Java 运行时环境 (JRE)。
生成原生代码
集成 Java 库时,需要使用封装容器工具生成原生代码封装容器。第一步是配置封装容器。
创建封装容器配置文件
您需要创建库封装容器配置文件来控制原生代码生成器的输出。该文件的功能之一是让您指定要生成封装容器代码的类和方法。
由于通知库没有太多方法需要封装,因此您可以直接在 custom_classes
部分定义这些方法。在项目的任意位置创建新的 config.json
资源,用于定义方法。例如,您可以创建 my_project/library_wrapper/config.json
并在其中粘贴以下示例配置:
{
"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)"
]
}
]
}
在上述示例中,您直接声明了需要原生封装容器代码的 Java 类和方法。
运行库封装容器
定义封装容器配置文件后,您就可以使用该工具生成原生封装容器代码了。打开一个连接到您提取库封装容器位置的终端,然后运行以下命令:
java -jar lw.jar \
-o "my_project/app/src/main/cpp/native_wrappers" \
-c "my_project/library_wrapper/config.json"
在上述示例中,您使用 -c
参数指定封装容器配置文件的位置,使用 -o
参数定义生成的代码所在的目录。运行该工具后,您现在应该已获得了生成的代码,可以使用这些代码从原生应用中调用基于 Java 的通知 API。
实现原生通知
在本部分中,您将使用生成的封装容器代码将 Android 通知库集成到原生应用中。第一步是更新项目的应用级 gradle.build
资源 (my_project/app/gradle.build
)。
更新 gradle.build
GNI 是生成的封装容器代码所需的支持库。所有使用生成的代码的项目都应引用此库。如需引用此库,请将以下代码行添加到
build.gradle
的dependencies
部分:implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
如需启用 prefab 支持,请将以下代码添加到
android
部分:buildFeatures { prefab true }
如需配置
cmake
,请在android/defaultConfig
部分中使用以下cmake
配置:externalNativeBuild { cmake { arguments '-DANDROID_STL=c++_shared' } }
完成后的 build.gradle
配置应与以下代码段类似:
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'
...
}
修改 CMakeLists
通过在项目的
CMakeLists.txt
(my_project/app/src/main/cpp/CMakeLists.txt
) 文件顶层添加以下代码行,将 GNI 库添加到该文件中:find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
将以下代码行添加到
target_link_libraries
部分:PUBLIC com.google.android.gms.gni.c::gni_shared
通过在该文件顶层添加以下代码行,添加对所生成代码的引用:
file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
在文件末尾附近添加以下代码行:
include_directories(./native_wrappers/c) include_directories(./native_wrappers/cpp)
更新后的 CMakeLists.txt
资源应与以下示例代码段类似:
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)
实现通知逻辑
打开或创建要在其中实现通知功能的源文件。在该文件中,添加头文件
gni.h
,并定义一个新函数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); }
定义通知专用常量值,以及通知处理程序函数
CharSequenceFromCString()
和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; }
通知库的部分函数接受
CharSequence
而不是String
。CharSequenceFromCString()
函数可以实现这些对象之间的转换。CreateNotification()
函数使用封装版的 JavaNotificationCompat.Builder
来创建通知。在以下函数
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); }
更新之前创建的
ShowNativeNotification()
函数以调用CreateNotificationChannel()
。将以下代码添加到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(¬ification); NotificationManagerCompat::destroy(¬ification_manager); }
定义逻辑后,在项目中的适当位置调用
ShowNativeNotification()
即会触发通知。
运行应用
编译并运行调用 ShowNativeNotification()
的代码。一个简单的通知应该会显示在测试设备的屏幕顶部。
通过 JAR 生成封装容器
在上面的示例中,您在封装容器配置文件中手动定义了需要原生代码的 Java 类和方法。如果您需要访问某个 API 的大部分内容,那么向封装容器工具提供一个或多个库 JAR 文件会更加高效。然后,封装容器工具会为它在 JAR 中找到的所有公共符号生成封装容器代码。
以下示例通过提供一个库 JAR 文件来封装整个 Notifications API。
获取所需的 JAR
Notification API 是 androidx.core
软件包的一部分,可通过 Google Maven 制品库获取。下载库 aar 文件并将其解压缩到您选择的目录,然后找到 classes.jar
文件。
除了本示例所需的通知库之外,classes.jar
文件还包含许多类。如果只为库封装容器提供 classes.jar
,工具会为 JAR 中的每个类生成原生代码,这对本项目来说是低效且没有必要的。为解决此问题,需要为封装容器配置文件提供一个过滤器文件,以便将代码生成范围限制为 JAR 的通知类。
定义允许过滤器
过滤器文件是您提供给库封装容器配置文件的纯文本文件。您可以在这类文件中定义提供给库封装容器的 JAR 文件中要包含(或排除)哪些类。
在项目中,创建一个名为 allowed-symbols.txt
的文件,并在其中粘贴以下代码行:
androidx.core.app.NotificationCompat*
当该文件用作允许过滤器时,上述代码会指定只有名称以 androidx.core.app.NotificationCompat
开头的符号会被封装。
运行库封装容器
打开一个连接到 JAR 目录的终端,然后运行以下命令:
java -jar lw.jar \
-i classes.jar \
-o "./generated-jar" \
-c "./config.json" \
-fa allowed-symbols.txt \
--skip_deprecated_symbols
上述示例命令会在目录 generated-jar/
中为过滤后的类生成封装容器代码。
支持
如果您发现库封装容器存在问题,请告知我们。
浏览 bug | 提交 bug |
---|---|
工程 | bug_report |
文档 | bug_report |