Przewodnik po kodzie biblioteki

Z tego przewodnika dowiesz się, jak używać otoki biblioteki interfejsów API Androida. Narzędzie wiersza poleceń biblioteki generuje kod otoki w języku C na potrzeby interfejsów API Java na Androida, co umożliwia integrację bibliotek Java z natywnymi aplikacjami w języku C/C++ na Androida. Więcej informacji o otoku biblioteki znajdziesz w artykule o opakowaniu biblioteki dla interfejsów API na Androida.

Ten szczegółowy przewodnik pokazuje, jak za pomocą narzędzia do pakowania aplikacji zintegrować bibliotekę Java z natywną aplikacją na Androida. Przewodnik zawiera na przykład informacje o integracji biblioteki powiadomień pakietu androidx.core.app. Aby dowiedzieć się więcej o tej bibliotece, zobacz Tworzenie powiadomienia.

Wymagania wstępne

W tym przewodniku zakładamy, że masz już natywny projekt na Androida. Wykorzystuje też system kompilacji Gradle. Jeśli nie masz jeszcze projektu, utwórz go w Android Studio, korzystając z szablonu Natywna w C++.

Przykładowy kod w tym przewodniku używa katalogu głównego my_project/. Kod natywny znajduje się w my_project/app/src/main/cpp/ – domyślnym katalogu projektów w Android Studio.

Jeśli nie masz jeszcze narzędzia do pakowania biblioteki, pobierz pakiet i rozpakuj go w wybranym katalogu. To narzędzie interfejsu wiersza poleceń wymaga środowiska wykonawczego Java (JRE).

Wygeneruj kod natywny

Podczas integracji biblioteki Java możesz użyć narzędzia opakowania, aby wygenerować natywny kod. Pierwszym krokiem jest skonfigurowanie kodu.

Tworzenie konfiguracji otoki

Tworzysz pliki konfiguracji kodu biblioteki, aby kontrolować dane wyjściowe generatora kodu natywnego. Jedna z funkcji tego pliku pozwala określić klasy i metody generowania kodu otoki.

Nie ma zbyt wielu metod zawijania wierszy w bibliotece powiadomień, dlatego możesz je zdefiniować bezpośrednio w sekcji custom_classes. Aby zdefiniować metody, utwórz nowy zasób config.json w dowolnym miejscu w projekcie. Możesz na przykład utworzyć my_project/library_wrapper/config.json i wkleić tę przykładową konfigurację:

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

W poprzednim przykładzie bezpośrednio zadeklarujesz klasy i metody Java, które wymagają natywnego kodu otoki.

Uruchamianie kodu biblioteki

Po zdefiniowaniu pliku konfiguracyjnego opakowania możesz użyć tego narzędzia do wygenerowania natywnego kodu otoki. Otwórz terminal, z którego wyodrębniono otokę biblioteki, i uruchom to polecenie:

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

W poprzednim przykładzie użyjesz parametru -c, aby określić lokalizację konfiguracji kodu, oraz parametru -o, aby zdefiniować wygenerowany katalog kodu. Po uruchomieniu narzędzia powinien być wygenerowany kod służący do wywoływania interfejsu API powiadomień opartego na języku Java z aplikacji natywnej.

Implementowanie powiadomień natywnych

W tej sekcji zintegrujesz bibliotekę powiadomień na Androidzie z aplikacją natywną, używając wygenerowanego kodu towarzyszącego. Pierwszym krokiem jest zaktualizowanie zasobu gradle.build na poziomie aplikacji (my_project/app/gradle.build) projektu.

Aktualizuj aplikację gradle.build

  1. GNI to biblioteka pomocy technicznej wymagana przez wygenerowany kod otoki. Wszystkie projekty korzystające z wygenerowanego kodu powinny się odwoływać do tej biblioteki. Aby odwołać się do tej biblioteki, dodaj ten wiersz do sekcji dependencies w build.gradle:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. Aby włączyć obsługę prefab, dodaj ten kod do sekcji android:

    buildFeatures {
      prefab true
    }
    
  3. Aby skonfigurować cmake, użyj tej konfiguracji cmake w sekcji android/defaultConfig:

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

Ukończona konfiguracja build.gradle powinna przypominać tę:

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

Zmień: CMakeLists

  1. Dodaj bibliotekę GNI do pliku CMakeLists.txt (my_project/app/src/main/cpp/CMakeLists.txt) projektu, dodając ten wiersz na najwyższym poziomie pliku:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. Do sekcji target_link_libraries dodaj ten wiersz:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. Dodaj odwołanie do wygenerowanego kodu, dodając ten wiersz na najwyższym poziomie pliku:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. Na końcu pliku dodaj te wiersze:

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

Zaktualizowany zasób CMakeLists.txt powinien przypominać ten przykładowy zasób:

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)

Wdrażanie logiki powiadomień

  1. Otwórz lub utwórz plik źródłowy, do którego chcesz zaimplementować możliwość wysyłania powiadomień. W tym pliku umieść plik nagłówka gni.h i zdefiniuj nową funkcję 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. Zdefiniuj stałe wartości dotyczące powiadomień oraz funkcje obsługi powiadomień CharSequenceFromCString() i 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;
    }
    

    Niektóre funkcje biblioteki powiadomień przejmują CharSequence zamiast String. Funkcja CharSequenceFromCString() umożliwia konwersję między tymi obiektami. Do tworzenia powiadomień funkcja CreateNotification() używa opakowanej wersji języka Java NotificationCompat.Builder.

  3. Dodaj funkcję CreateNotificationChannel(), aby utworzyć kanał powiadomień, wklejając:

    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. Zaktualizuj utworzoną wcześniej funkcję ShowNativeNotification(), aby wywoływać metodę CreateNotificationChannel(). Dodaj ten kod na końcu tekstu 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. Po zdefiniowaniu logiki aktywuj powiadomienie, wywołując powiadomienie ShowNativeNotification() w odpowiedniej lokalizacji w projekcie.

Uruchom aplikację

Skompiluj i uruchom kod wywołujący ShowNativeNotification(). Na górze ekranu urządzenia testowego powinno pojawić się proste powiadomienie.

Generowanie opakowań z plików JAR

W poprzednim przykładzie ręcznie zdefiniowane klasy i metody Javy wymagające kodu natywnego zostały zdefiniowane w pliku konfiguracyjnym kodu. W sytuacjach, gdy chcesz uzyskać dostęp do dużych sekcji interfejsu API, lepiej jest udostępnić co najmniej 1 biblioteczny plik JAR do narzędzia opakowu. Następnie kod generuje kody dla wszystkich symboli publicznych, które znajdzie w pliku JAR.

Poniższy przykład obejmuje cały interfejs Notification API, udostępniając bibliotekę JAR.

Pobieranie wymaganych plików JAR

Interfejs Notification API jest częścią pakietu androidx.core dostępnego w repozytorium Google Maven. Pobierz plik aar biblioteki i rozpakuj go do wybranego katalogu. Znajdź plik classes.jar.

Plik classes.jar zawiera wiele klas poza naszą wymaganą biblioteką powiadomień. Jeśli udostępnisz kod biblioteki tylko przy użyciu classes.jar, narzędzie wygeneruje kod natywny dla każdej klasy w pliku JAR, co będzie nieefektywne i niepotrzebne w naszym projekcie. Aby rozwiązać ten problem, udostępnij plik filtra do konfiguracji otoki, aby ograniczyć generowanie kodu do klas powiadomień JAR.

Zdefiniuj filtr zezwalający

Pliki filtrów to zwykłe pliki tekstowe, które udostępniasz w konfiguracji otoki biblioteki. Pozwalają one definiować, które klasy mają być uwzględniane (lub wykluczane) z plików JAR udostępnionych w kodzie biblioteki.

W projekcie utwórz plik o nazwie allowed-symbols.txt i wklej ten wiersz:

androidx.core.app.NotificationCompat*

Poprzedni kod używany jako filtr zezwalający określa, że opakowane są tylko symbole, których nazwa zaczyna się od androidx.core.app.NotificationCompat.

Uruchamianie kodu biblioteki

Otwórz terminal w katalogu JAR i uruchom to polecenie:

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

Poprzednie przykładowe polecenie generuje kod otoki dla przefiltrowanych klas w katalogu generated-jar/.

Pomoc

Jeśli zauważysz problem z kodem biblioteki, daj nam znać.

Przeglądaj błędy Zgłoś błąd
Inżynieria
Dokumentacja