ライブラリ ラッパーガイド

このガイドでは、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/ を使用しています。ネイティブ コードは、Android Studio プロジェクトのデフォルト ディレクトリ my_project/app/src/main/cpp/ にあります。

ライブラリ ラッパーツールをまだお持ちでない場合は、パッケージをダウンロードして、任意のディレクトリに解凍してください。この CLI ツールには Java Runtime Environment(JRE)が必要です。

ネイティブ コードを生成する

Java ライブラリを統合する場合は、ラッパーツールを使用してネイティブ コードラッパーを生成します。まず、ラッパーを構成します。

ラッパー構成を作成する

ライブラリ ラッパーの構成ファイルを作成して、ネイティブ コード生成ツールの出力を制御します。このファイルの 1 つの機能によって、ラッパーコードを生成するクラスとメソッドを指定できます。

通知ライブラリについては、ラップするための方法が多くは存在しないため、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 ベースの Notifications 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.txtmy_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;
    }
    

    通知ライブラリの一部の関数は、String ではなく CharSequence を受け取ります。CharSequenceFromCString() 関数を使用すると、これらのオブジェクト間での変換が可能になります。関数 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 の大規模なセクションにアクセスする必要があるシナリオでは、ラッパーツールに 1 つ以上のライブラリ 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/ に生成します。

サポート

ライブラリのラッパーに問題がある場合は、お知らせください。

バグを参照する バグを報告する
エンジニアリング
ドキュメント