程式庫包裝函式指南

本指南說明如何使用 Android API 程式庫包裝函式。程式庫包裝函式指令列工具可以為 Java Android API 產生 C 語言包裝函式程式碼,讓您在原生 C/C++ Android 應用程式中整合 Java 程式庫。如要進一步瞭解程式庫包裝函式,請參閱「適用於 Android API 的程式庫包裝函式」。

本逐步指南將說明如何使用包裝函式工具,在原生 Android 應用程式中整合 Java 程式庫。舉例來說,本指南會說明如何整合 androidx.core.app 套件的通知程式庫。如要進一步瞭解這個程式庫,請參閱「建立通知」。

必要條件

本指南假設您已有原生 Android 專案,且該專案也使用 Gradle 建構系統。如果目前沒有專案,請在 Android Studio 中使用原生 C++ 範本建立新專案。

本指南中的範例程式碼使用根目錄 my_project/。原生程式碼位於 my_project/app/src/main/cpp/,這是 Android Studio 專案的預設目錄。

如果目前沒有程式庫包裝函式工具,請下載套件,並將套件解壓縮至選擇的目錄。這個 CLI 工具需要 Java Runtime Environment (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. 請更新先前為了呼叫 CreateNotificationChannel() 而建立的 ShowNativeNotification() 函式。將下列程式碼新增至 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,包裝整個 Notification 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/ 目錄。

支援

如果發現程式庫包裝函式有問題,請通知我們。

瀏覽錯誤 回報錯誤
工程
說明文件