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
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
wbuild.gradle
:implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
Aby włączyć obsługę prefab, dodaj ten kod do sekcji
android
:buildFeatures { prefab true }
Aby skonfigurować
cmake
, użyj tej konfiguracjicmake
w sekcjiandroid/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
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)
Do sekcji
target_link_libraries
dodaj ten wiersz:PUBLIC com.google.android.gms.gni.c::gni_shared
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")
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ń
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); }
Zdefiniuj stałe wartości dotyczące powiadomień oraz funkcje obsługi powiadomień
CharSequenceFromCString()
iCreateNotification()
: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
zamiastString
. FunkcjaCharSequenceFromCString()
umożliwia konwersję między tymi obiektami. Do tworzenia powiadomień funkcjaCreateNotification()
używa opakowanej wersji języka JavaNotificationCompat.Builder
.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(¬ification_manager_as_object); NotificationManager::destroy(notification_manager); }
Zaktualizuj utworzoną wcześniej funkcję
ShowNativeNotification()
, aby wywoływać metodęCreateNotificationChannel()
. Dodaj ten kod na końcu tekstuShowNativeNotification()
: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(¬ification); NotificationManagerCompat::destroy(¬ification_manager); }
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 | bug_report |
Dokumentacja | bug_report |