Guia do wrapper de biblioteca

Este guia descreve como usar o wrapper de biblioteca da API Android. A ferramenta de linha de comando do wrapper de biblioteca gera código de wrapper em linguagem C para APIs Java do Android, permitindo que você integre bibliotecas Java em apps Android C/C++ nativos. Para conferir mais detalhes sobre o wrapper de biblioteca, consulte Wrapper de biblioteca para APIs do Android.

Este guia explicativo demonstra como usar a ferramenta de wrapper para integrar uma biblioteca Java a um app Android nativo. Por exemplo, ele aborda a integração da biblioteca de notificações do pacote androidx.core.app. Consulte Criar uma notificação para saber mais sobre essa biblioteca.

Pré-requisitos

Este guia pressupõe que você já tenha um projeto Android nativo. Ele também usa o sistema de build do Gradle. Se você ainda não tem um projeto, crie um no Android Studio usando o modelo Native C++.

O código de exemplo neste guia usa a raiz my_project/ do diretório. O código nativo está localizado em my_project/app/src/main/cpp/, que é o diretório padrão para projetos do Android Studio.

Se você ainda não tem a ferramenta de wrapper de biblioteca, faça o download e descompacte o pacote no diretório que preferir. A ferramenta da CLI requer o Java Runtime Environment (JRE).

Gerar código nativo

Ao integrar uma biblioteca Java, use a ferramenta de wrapper para gerar um wrapper de código nativo. A primeira etapa é configurar o wrapper.

Criar a configuração do wrapper

Crie arquivos de configuração do wrapper de biblioteca para controlar a saída do gerador de código nativo. Um recurso desse arquivo permite especificar as classes e os métodos para gerar o código do wrapper.

Como não há muitos métodos para unir em relação à biblioteca de notificações, eles podem ser definidos diretamente na seção custom_classes. Crie um recurso config.json em qualquer lugar do projeto para definir os métodos. Por exemplo, é possível criar my_project/library_wrapper/config.json e colar a seguinte configuração de exemplo:

{
  "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)"
      ]
    }
  ]
}

No exemplo anterior, você declara diretamente as classes e os métodos Java que exigem código do wrapper nativo.

Executar o wrapper de biblioteca

Com o arquivo de configuração do wrapper definido, é possível usar a ferramenta para gerar o código do wrapper nativo. Abra um terminal para onde você extraiu o wrapper de biblioteca e execute o seguinte comando:

java -jar lw.jar \
  -o "my_project/app/src/main/cpp/native_wrappers" \
  -c "my_project/library_wrapper/config.json"

No exemplo anterior, você usa o parâmetro -c para especificar o local de configuração do wrapper e o parâmetro -o para definir o diretório do código gerado. Depois de executar a ferramenta, você terá o código gerado necessário para chamar a API de notificações baseada em Java no seu app nativo.

Implementar notificações nativas

Nesta seção, você vai integrar a biblioteca de notificações do Android ao app nativo usando o código do wrapper gerado. A primeira etapa é atualizar o recurso gradle.build no nível do app do seu projeto (my_project/app/gradle.build).

Atualizar o gradle.build

  1. A GNI é uma biblioteca de suporte exigida pelo código do wrapper gerado. Todos os projetos que usam o código gerado precisam indicar essa biblioteca. Para fazer a indicação, adicione a seguinte linha à seção dependencies de build.gradle:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Para ativar o suporte a prefab, adicione o seguinte código à seção android:

    buildFeatures {
      prefab true
    }
    
  3. Para definir o cmake, use a seguinte configuração de cmake na seção android/defaultConfig:

    externalNativeBuild {
      cmake {
          arguments '-DANDROID_STL=c++_shared'
      }
    }
    

A configuração do build.gradle concluída terá esta aparência:

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'
    ...
}

Modificar CMakeLists

  1. Adicione a biblioteca GNI ao CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt) do seu projeto incluindo a seguinte linha no nível superior do arquivo:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Adicione a linha abaixo à seção target_link_libraries:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Adicione uma referência ao código gerado incluindo a linha abaixo ao nível superior do arquivo:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Adicione estas linhas ao final do arquivo:

    include_directories(./native_wrappers/c)
    include_directories(./native_wrappers/cpp)
    

Seu recurso CMakeLists.txt atualizado será semelhante a este exemplo:

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)

Implementar a lógica de notificação

  1. Abra ou crie o arquivo de origem em que você quer implementar recursos de notificação. Nele, inclua o arquivo principal gni.h e defina uma nova função 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. Defina valores constantes específicos para notificação e as funções CharSequenceFromCString() e CreateNotification() do gerenciador de notificações:

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

    Algumas funções da biblioteca de notificações usam CharSequence em vez de String. A função CharSequenceFromCString() permite a conversão entre esses objetos. A CreateNotification() usa a versão unida da classe NotificationCompat.Builder do Java para criar uma notificação.

  3. Adicione lógica para criar um canal de notificação colando a função 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. Atualize a função ShowNativeNotification() criada anteriormente para chamar CreateNotificationChannel(). Adicione o seguinte código ao final de 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. Com sua lógica definida, acione uma notificação chamando ShowNativeNotification() em um local adequado no projeto.

Executar o app

Compile e execute o código que chama ShowNativeNotification(). Uma notificação simples será mostrada na parte de cima da tela do dispositivo de teste.

Gerar wrappers com JARs

No exemplo anterior, você definiu manualmente as classes e os métodos Java que exigem código nativo em um arquivo de configuração do wrapper. Para cenários em que você precisa acessar grandes seções de uma API, é mais eficiente fornecer um ou mais JARs de biblioteca para a ferramenta de wrapper. Em seguida, wrappers são gerados para todos os símbolos públicos encontrados no JAR.

O exemplo a seguir une toda a API Notifications fornecendo uma biblioteca JAR.

Acessar os JARs necessários

A API Notification faz parte do pacote androidx.core, disponível no repositório Maven do Google. Faça o download do arquivo aar da biblioteca e descompacte-o em um diretório de sua escolha. Localize o arquivo classes.jar.

O arquivo classes.jar contém muitas classes além da biblioteca de notificações necessária. Se você fornecer o wrapper de biblioteca apenas com o classes.jar, a ferramenta vai gerar código nativo para cada classe no JAR, o que é ineficiente e desnecessário para nosso projeto. Para resolver esse problema, forneça um arquivo de filtro à configuração do wrapper para restringir a geração de código às classes de notificação do JAR.

Definir um filtro de permissão

Os arquivos de filtro são arquivos de texto simples fornecidos para a configuração do wrapper de biblioteca. Eles permitem que você defina quais classes serão incluídas ou excluídas dos arquivos JAR fornecidos no wrapper de biblioteca.

No projeto, crie um arquivo intitulado allowed-symbols.txt e cole nesta linha:

androidx.core.app.NotificationCompat*

Quando usado como filtro de permissão, o código anterior especifica que apenas os símbolos com nomes que começam com androidx.core.app.NotificationCompat são unidos.

Executar o wrapper de biblioteca

Abra um terminal para o diretório JAR e execute o seguinte comando:

java -jar lw.jar \
 -i classes.jar \
 -o "./generated-jar" \
 -c "./config.json" \
 -fa allowed-symbols.txt \
 --skip_deprecated_symbols

O comando de exemplo anterior gera um código de wrapper das classes filtradas para o diretório generated-jar/.

Suporte

Se você encontrar um problema com o wrapper de biblioteca, entre em contato.

Procurar bugs Informar um bug
Engenharia
Documentação