Руководство по обертке библиотеки

В этом руководстве описывается, как использовать оболочку библиотеки Android API. Инструмент командной строки оболочки библиотеки генерирует код оболочки на языке C для API-интерфейсов Java Android, что позволяет интегрировать библиотеки Java в собственные приложения C/C++ для Android. Дополнительные сведения об оболочке библиотеки см. в разделе Оболочка библиотеки для API Android .

В этом пошаговом руководстве показано, как использовать инструмент-оболочку для интеграции библиотеки 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 , чтобы определить каталог сгенерированного кода. После запуска инструмента у вас должен быть сгенерированный код, необходимый для вызова API уведомлений на основе Java из вашего собственного приложения.

Внедрить встроенные уведомления

В этом разделе вы интегрируете библиотеку уведомлений Android в свое собственное приложение, используя сгенерированный код оболочки. Первым шагом является обновление ресурса gradle.build вашего проекта на уровне приложения ( my_project/app/gradle.build ).

Обновить gradle.build

  1. GNI — это библиотека поддержки, необходимая для сгенерированного кода оболочки. Все проекты, использующие сгенерированный код, должны ссылаться на эту библиотеку. Чтобы сослаться на эту библиотеку, добавьте следующую строку в раздел dependencies build.gradle :

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Чтобы включить поддержку префабов , добавьте следующий код в раздел android :

    buildFeatures {
      prefab true
    }
    
  3. Чтобы настроить cmake , используйте следующую конфигурацию cmake в разделе android/defaultConfig :

    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() :

    С

    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;
    }
    

    С++

    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() использует обернутую версию Java NotificationCompat.Builder для создания уведомления.

  3. Добавьте логику для создания канала уведомлений, вставив следующую функцию CreateNotificationChannel() :

    С

    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);
    }
    

    С++

    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() :

    С

    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);
    }
    

    С++

    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.

В следующем примере весь API уведомлений включает в себя JAR-файл библиотеки.

Получите необходимые 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 generated-jar/ .

Поддерживать

Если вы обнаружите проблему с оболочкой библиотеки, сообщите нам об этом.

Просмотр ошибок Сообщить об ошибке
Инженерное дело
Документация