라이브러리 래퍼 가이드

이 가이드에서는 Android API 라이브러리 래퍼를 사용하는 방법을 설명합니다. 라이브러리 래퍼 명령줄 도구는 Java Android API용 C 언어 래퍼 코드를 생성하므로 Java 라이브러리를 네이티브 C/C++ Android 앱에 통합할 수 있습니다. 라이브러리 래퍼에 관한 자세한 내용은 Android API용 라이브러리 래퍼를 참고하세요.

이 단계별 가이드에서는 래퍼 도구를 사용하여 Java 라이브러리를 네이티브 Android 앱에 통합하는 방법을 보여줍니다. 예를 들어 이 가이드에서는 androidx.core.app 패키지의 알림 라이브러리를 통합하는 방법을 설명합니다. 이 라이브러리에 관한 자세한 내용은 알림 만들기를 참고하세요.

기본 요건

이 가이드에서는 기존 네이티브 Android 프로젝트가 있다고 가정합니다. 또한 Gradle 빌드 시스템을 사용합니다. 기존 프로젝트가 없다면 Android 스튜디오에서 Native C++ 템플릿을 사용하여 새 프로젝트를 만듭니다.

이 가이드에서 예로 든 코드에서는 루트 디렉터리 my_project/를 사용합니다. 네이티브 코드는 Android 스튜디오 프로젝트의 기본 디렉터리인 my_project/app/src/main/cpp/에 있습니다.

아직 라이브러리 래퍼 도구가 없다면 패키지를 다운로드하여 원하는 디렉터리에 압축해제하세요. 이 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. 파일 최상위에 다음 줄을 추가하여 프로젝트의 CMakeLists.txt(my_project/app/src/main/cpp/CMakeLists.txt)에 GNI 라이브러리를 추가합니다.

    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의 큰 섹션에 액세스해야 하는 시나리오의 경우 래퍼 도구에 하나 이상의 라이브러리 JAR을 제공하는 것이 더 효율적입니다. 그러면 래퍼가 JAR에서 찾은 모든 공개 기호의 래퍼를 생성합니다.

다음 예에서는 라이브러리 JAR을 제공하여 알림 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/ 디렉터리에 생성합니다.

지원

라이브러리 래퍼에 문제가 있으면 Google에 알려주세요.

버그 검색 버그 신고
엔지니어링
문서