Leitfaden zum Wrapper für Bibliothek

In diesem Leitfaden wird die Verwendung des Wrappers der Android API-Bibliothek beschrieben. Das Befehlszeilentool „Library Wrapper“ generiert Wrapper-Code in C-Sprache für Java Android APIs, sodass Sie Java-Bibliotheken in native C/C++ Android-Apps einbinden können. Weitere Informationen zum Bibliotheks-Wrapper findest du unter Bibliothek-Wrapper für Android APIs.

In dieser detaillierten Anleitung wird beschrieben, wie Sie mit dem Wrapper-Tool eine Java-Bibliothek in eine native Android-App einbinden. In dieser Anleitung wird beispielsweise die Integration der Benachrichtigungsbibliothek im androidx.core.app-Paket beschrieben. Weitere Informationen zu dieser Bibliothek findest du unter Benachrichtigung erstellen.

Voraussetzungen

In dieser Anleitung wird davon ausgegangen, dass Sie bereits ein natives Android-Projekt haben. Außerdem wird dafür das Build-System Gradle verwendet. Wenn du noch kein Projekt hast, erstelle ein neues in Android Studio mit der Vorlage Native C++.

Im Beispielcode in diesem Leitfaden wird der Verzeichnisstamm my_project/ verwendet. Der native Code befindet sich in my_project/app/src/main/cpp/, dem Standardverzeichnis für Android Studio-Projekte.

Wenn Sie das Bibliotheks-Wrapper-Tool noch nicht haben, laden Sie das Paket herunter und entpacken Sie es im Verzeichnis Ihrer Wahl. Für dieses Befehlszeilen-Tool ist die Java-Laufzeitumgebung (JRE) erforderlich.

Nativen Code generieren

Verwenden Sie beim Einbinden einer Java-Bibliothek das Wrapper-Tool, um einen nativen Code-Wrapper zu generieren. Der erste Schritt besteht darin, den Wrapper zu konfigurieren.

Wrapper-Konfiguration erstellen

Sie erstellen Konfigurationsdateien für Bibliotheks-Wrapper, um die Ausgabe des nativen Codegenerators zu steuern. Mit einer Funktion dieser Datei können Sie die Klassen und Methoden zum Generieren von Wrapper-Code angeben.

Da es nicht viele Methoden zum Zusammenfassen für die Benachrichtigungsbibliothek gibt, können Sie diese direkt im Abschnitt custom_classes definieren. Erstellen Sie an einer beliebigen Stelle in Ihrem Projekt eine neue config.json-Ressource, um die Methoden zu definieren. Sie können beispielsweise my_project/library_wrapper/config.json erstellen und die folgende Beispielkonfiguration einfügen:

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

Im vorherigen Beispiel deklarieren Sie direkt die Java-Klassen und -Methoden, die nativen Wrapper-Code erfordern.

Bibliothek-Wrapper ausführen

Nachdem Sie die Wrapper-Konfigurationsdatei definiert haben, können Sie mit dem Tool nativen Wrapper-Code generieren. Öffnen Sie ein Terminal, in das Sie den Bibliothek-Wrapper extrahiert haben, und führen Sie den folgenden Befehl aus:

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

Im vorherigen Beispiel verwenden Sie den Parameter -c, um den Speicherort der Wrapper-Konfiguration anzugeben, und den Parameter -o, um das Verzeichnis mit dem generierten Code zu definieren. Nachdem Sie das Tool ausgeführt haben, sollten Sie den generierten Code haben, der zum Aufrufen der Java-basierten Notifications API aus Ihrer nativen App erforderlich ist.

Native Benachrichtigungen implementieren

In diesem Abschnitt integrieren Sie die Android-Benachrichtigungsbibliothek mithilfe des generierten Wrapper-Codes in Ihre native App. Der erste Schritt besteht darin, die gradle.build-Ressource Ihres Projekts (my_project/app/gradle.build) auf App-Ebene zu aktualisieren.

gradle.build aktualisieren

  1. GNI ist eine Supportbibliothek, die für den generierten Wrapper-Code benötigt wird. Alle Projekte, die generierten Code verwenden, sollten auf diese Bibliothek verweisen. Fügen Sie dem Abschnitt dependencies von build.gradle die folgende Zeile hinzu, um auf diese Bibliothek zu verweisen:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Um die prefab-Unterstützung zu aktivieren, fügen Sie den folgenden Code in den Abschnitt android ein:

    buildFeatures {
      prefab true
    }
    
  3. Verwenden Sie die folgende cmake-Konfiguration im Abschnitt android/defaultConfig, um cmake zu konfigurieren:

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

Ihre abgeschlossene build.gradle-Konfiguration sollte in etwa so aussehen:

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 ändern

  1. Fügen Sie die GNI-Bibliothek zum CMakeLists.txt Ihres Projekts (my_project/app/src/main/cpp/CMakeLists.txt) hinzu, indem Sie die folgende Zeile auf oberster Ebene der Datei einfügen:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Fügen Sie dem Abschnitt target_link_libraries die folgende Zeile hinzu:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Fügen Sie dem generierten Code einen Verweis hinzu, indem Sie die folgende Zeile auf der obersten Ebene der Datei hinzufügen:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Fügen Sie diese Zeilen am Ende der Datei ein:

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

Die aktualisierte Ressource CMakeLists.txt sollte in etwa so aussehen:

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)

Benachrichtigungslogik implementieren

  1. Öffnen oder erstellen Sie die Quelldatei, in der Sie Benachrichtigungsfunktionen implementieren möchten. Fügen Sie in dieser Datei die Headerdatei gni.h ein und definieren Sie eine neue ShowNativeNotification()-Funktion:

    #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. Definieren Sie benachrichtigungsspezifische konstante Werte und der Benachrichtigungs-Handler funktioniert CharSequenceFromCString() und 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;
    }
    

    Einige Funktionen der Benachrichtigungsbibliothek verwenden CharSequence anstelle von String. Die Funktion CharSequenceFromCString() ermöglicht die Konvertierung zwischen diesen Objekten. Die Funktion CreateNotification() verwendet die umschlossene Version von Java NotificationCompat.Builder, um eine Benachrichtigung zu erstellen.

  3. Fügen Sie die Logik zum Erstellen eines Benachrichtigungskanals hinzu, indem Sie die folgende Funktion CreateNotificationChannel() einfügen:

    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. Aktualisieren Sie die zuvor erstellte Funktion ShowNativeNotification(), um CreateNotificationChannel() aufzurufen. Fügen Sie am Ende von ShowNativeNotification() den folgenden Code ein:

    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. Wenn Ihre Logik definiert ist, können Sie eine Benachrichtigung auslösen, indem Sie ShowNativeNotification() an einer entsprechenden Stelle im Projekt aufrufen.

App ausführen

Kompilieren Sie den Code, der ShowNativeNotification() aufruft, und führen Sie ihn aus. Oben auf dem Bildschirm des Testgeräts sollte eine einfache Benachrichtigung angezeigt werden.

Wrapper aus JARs generieren

Im vorherigen Beispiel haben Sie Java-Klassen und -Methoden, die nativen Code in einer Wrapper-Konfigurationsdatei erfordern, manuell definiert. Wenn Sie auf große Bereiche einer API zugreifen müssen, ist es effizienter, dem Wrapper-Tool eine oder mehrere Bibliotheks-JARs bereitzustellen. Der Wrapper generiert dann Wrapper für alle in der JAR-Datei gefundenen öffentlichen Symbole.

Im folgenden Beispiel wird die gesamte Notifications API umschlossen, indem eine Bibliotheks-JAR-Datei bereitgestellt wird.

Besorgen Sie sich die erforderlichen JARs

Die Notification API ist Teil des Pakets androidx.core, das im Google Maven-Repository verfügbar ist. Laden Sie die aar-Bibliotheksdatei herunter und entpacken Sie sie in ein Verzeichnis Ihrer Wahl. Suchen Sie die Datei classes.jar.

Die Datei classes.jar enthält viele Klassen außerhalb der erforderlichen Benachrichtigungsbibliothek. Wenn Sie den Bibliothek-Wrapper nur mit classes.jar bereitstellen, generiert das Tool nativen Code für jede Klasse in der JAR-Datei. Dies ist für unser Projekt ineffizient und unnötig. Stellen Sie zur Behebung dieses Problems eine Filterdatei für die Wrapper-Konfiguration bereit, um die Codegenerierung auf die Benachrichtigungsklassen der JAR-Datei zu beschränken.

Zulassungsfilter definieren

Filterdateien sind Nur-Text-Dateien, die Sie in Ihrer Bibliotheks-Wrapper-Konfiguration angeben. Damit können Sie definieren, welche Klassen in die JAR-Dateien, die für den Bibliotheks-Wrapper bereitgestellt werden, einbezogen oder davon ausgeschlossen werden sollen.

Erstellen Sie in Ihrem Projekt eine Datei mit dem Namen allowed-symbols.txt und fügen Sie sie in die folgende Zeile ein:

androidx.core.app.NotificationCompat*

Bei Verwendung als „allow“-Filter gibt der vorherige Code an, dass nur Symbole, deren Name mit androidx.core.app.NotificationCompat beginnt, umschlossen werden.

Bibliothek-Wrapper ausführen

Öffnen Sie ein Terminal zum JAR-Verzeichnis und führen Sie den folgenden Befehl aus:

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

Mit dem vorherigen Beispielbefehl wird Wrapper-Code für Ihre gefilterten Klassen im Verzeichnis generated-jar/ generiert.

Support

Wenn Sie ein Problem mit dem Bibliotheks-Wrapper feststellen, teilen Sie uns dies bitte mit.

Programmfehler durchsuchen Programmfehler melden
Technik
Dokumentation