库封装器指南

本指南介绍了如何使用 Android API 库封装器。库封装器命令行工具可为 Java Android API 生成 C 语言封装器代码,使您能够将 Java 库集成到原生 C/C++ Android 应用中。有关库封装器的更多详情,请参阅适用于 Android API 的库封装器

本分步指南演示了如何使用封装器工具将 Java 库集成到原生 Android 应用中。例如,本指南介绍了如何集成 androidx.core.app 软件包的通知库。如需详细了解此库,请参阅创建通知

前提条件

本指南假定您拥有现有的原生 Android 项目。它还使用 Gradle 构建系统。如果您没有现有项目,请在 Android Studio 中使用 Native 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

  1. GNI 是生成封装器代码所需的辅助库。所有使用生成代码的项目都应引用此库。要引用此库,请将以下行添加到 build.gradledependencies 部分

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. 要启用 prefab 支持,请将以下代码添加到 android 部分

    buildFeatures {
      prefab true
    }
    
  3. 要配置 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

  1. 通过在文件的顶层添加以下行,将 GNI 库添加到项目的 CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt)

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. 将以下行添加到 target_link_libraries 部分

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. 通过在文件的顶层添加以下行,添加对生成代码的引用

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. 将这些行添加到文件末尾附近

    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)

实现通知逻辑

  1. 打开或创建您希望实现通知功能的源文件。在此文件中,包含头文件 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);
    }
    
  2. 定义通知特定的常量值,以及通知处理函数 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 而不是 StringCharSequenceFromCString() 函数可以实现这些对象之间的转换。CreateNotification() 函数使用 Java NotificationCompat.Builder 的封装版本来创建通知。

  3. 通过粘贴以下函数 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. 更新您之前创建的 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(&notification);
       NotificationManagerCompat::destroy(&notification_manager);
    }   
  5. 定义好逻辑后,在项目中的适当位置调用 ShowNativeNotification() 以触发通知。

运行应用

编译并运行调用 ShowNativeNotification() 的代码。一个简单的通知应出现在您的测试设备屏幕顶部。

从 JAR 生成封装器

在前面的示例中,您在封装器配置文件中手动定义了需要原生代码的 Java 类和方法。对于需要访问 API 大部分内容的情况,更高效的方法是向封装器工具提供一个或多个 JAR 库。然后,封装器会为它在 JAR 中找到的所有公共符号生成封装器。

以下示例通过提供 JAR 库来封装整个通知 API。

获取所需的 JAR

通知 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
工程
文档