JNI to natywny interfejs Java. Określa sposób kodowania bajtowego, na podstawie którego Android kompiluje kod zarządzany (napisany w języku Java lub Kotlin) umożliwiający interakcję z kodem natywnym (napisane w języku C/C++). JNI działa niezależnie od dostawcy i obsługuje wczytywanie kodu z dynamicznego udostępniania i chociaż czasem jest to niewygodne, działa rozsądnie.
Uwaga: Android kompiluje Kotlin na kod bajtowy zgodny z ART podobnie jak w języku Java, możesz zastosować wskazówki na tej stronie zarówno w językach programowania Kotlin i Java w kontekście architektury JNI i powiązanych z nimi kosztów. Więcej informacji: Kotlin i Android.
Jeśli jeszcze tego nie znasz, przeczytaj Specyfikacja interfejsu natywnego Java aby zorientować się, jak działa JNI i jakie funkcje są dostępne. Niektóre aspekty interfejsu nie są od razu widoczne , więc może Ci się przydać kilka kolejnych sekcji.
Aby przeglądać globalne odniesienia JNI i sprawdzić, gdzie tworzone i usuwane są globalne odwołania do JNI, użyj widok sterty JNI w narzędziu Memory Profiler. w Android Studio 3.2 i nowszych wersjach.
Wskazówki ogólne
Postaraj się zminimalizować rozmiar warstwy JNI. Należy tu wziąć pod uwagę kilka wymiarów. Twoje rozwiązanie JNI powinno być zgodne z tymi wytycznymi (wymienione poniżej w kolejności według ważności, zaczynając od najważniejszego):
- Ogranicz grupowanie zasobów w warstwie JNI. Zbiórka przez warstwa JNI wiąże się z prostymi kosztami. Postaraj się zaprojektować interfejs, który będzie ograniczać do minimum ilość dane, które chcesz grupować, oraz częstotliwość, z jaką należy je grupować.
- Unikaj asynchronicznego komunikacji między kodem napisanym w programie zarządzanym w językach i kodach napisanych w języku C++, jeśli to możliwe. Ułatwi to obsługę interfejsu JNI. Zwykle można uprościć proces asynchroniczny Aktualizuje interfejs asynchronicznie w tym samym języku co interfejs użytkownika. Na przykład zamiast przez JNI, lepiej jest wywołać funkcję C++ z wątku UI w kodzie Java, aby wykonać wywołanie zwrotne między dwoma wątkami w języku programowania Java, z których jeden nawiązywanie blokującego wywołania C++, a następnie powiadamianie wątku UI o tym, że gotowe.
- Zminimalizuj liczbę wątków, które mają być dotykane lub dotykane przez JNI. Jeśli musisz używać pul wątków zarówno w językach Java, jak i C++, spróbuj zachować JNI między właścicielami puli, a nie między poszczególnymi wątkami instancji roboczych.
- Umieść kod interfejsu w niewielkiej liczbie łatwo rozpoznawalnych źródeł w językach C++ i Java. lokalizacji ułatwiające przyszłe refaktoryzacje. Rozważ użycie automatycznego generowania JNI z biblioteki.
JavaVM i JNIEnv
JNI definiuje 2 kluczowe struktury danych: „JavaVM” i „JNIEnv”. Oba te elementy są zasadniczo wskaźniki do tabel funkcji. (W wersji C++ są to klasy z i wskaźnik do tabeli funkcji oraz funkcji składowej każdej funkcji JNI, która pośrednia przez tabeli). JavaVM zapewnia „interfejs wywołania” funkcje, które pozwalają utworzyć i zniszczyć maszynę wirtualną JavaVM. Teoretycznie można mieć wiele maszyn JavaVM na proces, a Android dopuszcza tylko jedną z nich.
JNIEnv zapewnia większość funkcji JNI. Wszystkie funkcje natywne otrzymują JNIEnv jako
pierwszego argumentu, z wyjątkiem metod @CriticalNative
,
zobacz szybsze wywołania natywne.
Protokół JNIEnv jest używany do obsługi pamięci lokalnej w wątku. Z tego powodu nie można udostępniać JNIEnv między wątkami.
Jeśli dla fragmentu kodu nie ma innego sposobu uzyskania JNIEnv, udostępnij
JavaVM i użyć narzędzia GetEnv
, aby wykryć komponent JNIEnv wątku. (Zakładając, że ją ma; patrz AttachCurrentThread
poniżej).
Deklaracje C w JNIEnv i JavaVM różnią się od deklaracji w C++
deklaracje. Plik „"jni.h"
” zawiera różne definicje typów definicji
w zależności od tego, czy znajduje się on w języku C czy C++. Z tego powodu nie należy
uwzględnij argumenty JNIEnv w plikach nagłówka dołączanych do obu języków. (Użyj innej metody: jeśli
plik nagłówka wymaga parametru #ifdef __cplusplus
. W razie potrzeby wykonanie dodatkowych czynności może być
ten nagłówek odnosi się do JNIEnv).
Wątki
Wszystkie wątki to wątki Linuksa zaplanowane przez jądro. Zazwyczaj są to
uruchomiono za pomocą kodu zarządzanego (przy użyciu: Thread.start()
),
ale można je też utworzyć w innym miejscu, a potem dołączyć do elementu JavaVM
. Dla:
przykład, wątek zaczynający się od pthread_create()
lub std::thread
można dołączyć za pomocą AttachCurrentThread()
lub
AttachCurrentThreadAsDaemon()
. Do momentu, gdy wątek zostanie
nie ma przypisanego JNIEnv i nie może wykonywać wywołań JNI.
Zwykle najlepiej jest użyć usługi Thread.start()
do utworzenia dowolnego wątku, który musi
do kodu Java. Dzięki temu będziesz mieć wystarczającą ilość miejsca na stosie,
w odpowiednim polu ThreadGroup
oraz że używasz tego samego identyfikatora ClassLoader
jako kodu w Javie. Łatwiejsze jest też ustawienie nazwy wątku na potrzeby debugowania w języku Java niż
kodu natywnego (zobacz pthread_setname_np()
, jeśli masz pthread_t
lub
thread_t
i std::thread::native_handle()
, jeśli masz
std::thread
i chcesz pthread_t
).
Dołączenie wątku utworzonego natywnie powoduje java.lang.Thread
obiekt, który ma zostać skonstruowany i dodany do obiektu głównego ThreadGroup
,
przez co jest widoczna dla debugera. Dzwonię pod AttachCurrentThread()
w już załączonym wątku jest no-op.
Android nie zawiesza wątków wykonujących kod natywny. Jeśli trwa usuwanie czyszczenia lub debuger zawiesił dane Android wstrzyma wątek przy następnym wywołaniu JNI.
Wątki dołączone przez JNI wymagają wywołania
DetachCurrentThread()
przed ich zamknięciem.
Jeśli kodowanie bezpośrednio jest niezręczne, na Androidzie 2.0 (Eclair) i nowszych
może użyć funkcji pthread_key_create()
do zdefiniowania destruatora
funkcję, która zostanie wywołana przed zakończeniem wątku, oraz
zadzwonić pod numer DetachCurrentThread()
. (Użyj tego
klucz z pthread_setspecific()
do przechowywania JNIEnv w
thread-local-storage; w ten sposób zostanie on przekazany do destruktora jako
argumentu).
jclass, jmethodID i jfieldID
Jeśli chcesz uzyskać dostęp do pola obiektu z kodu natywnego, wykonaj te czynności:
- Pobierz odniesienie do obiektu klasy za pomocą
FindClass
- Pobierz identyfikator pola za pomocą funkcji
GetFieldID
- Pobierz odpowiednią zawartość pola, np.
GetIntField
Podobnie, aby wywołać metodę, musisz najpierw uzyskać odniesienie do obiektu klasy, a następnie identyfikator metody. Identyfikatory te są często po prostu i wskaźniki do wewnętrznych struktur danych w czasie działania. Wyszukiwanie ich może wymagać kilku ciągów znaków ale gdy je otrzymasz, następuje wywołanie pobrania pola lub metody bardzo szybko.
Jeśli wydajność jest ważna, warto wyszukać wartości raz i zapisać wyniki w pamięci podręcznej w kodzie natywnym. Obowiązuje limit jednej maszyny JavaVM na proces, więc do przechowywania tych danych w statycznej strukturze lokalnej.
Odwołania do klas, identyfikatorów pól i metod pozostają ważne do momentu wyładowania klasy. Zajęcia
są wyładowywane tylko wtedy, gdy wszystkie klasy powiązane z klasą ClassLoader mogą zostać wyczyszczone,
co zdarza się rzadko, ale na Androidzie nie będzie niemożliwe. Pamiętaj jednak, że
jclass
jest odwołaniem do klasy i musi być chronione za pomocą wywołania
do NewGlobalRef
(patrz następna sekcja).
Jeśli chcesz zapisywać identyfikatory w pamięci podręcznej podczas wczytywania klasy i automatycznie umieszczać je w pamięci podręcznej jeśli klasa zostanie wyładowana i załadowana ponownie, poprawny sposób jej zainicjowania należy dodać do odpowiedniej klasy fragment kodu, który wygląda tak:
Kotlin
companion object { /* * We use a static class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private external fun nativeInit() init { nativeInit() } }
Java
/* * We use a class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private static native void nativeInit(); static { nativeInit(); }
Utwórz w kodzie C/C++ metodę nativeClassInit
, która wyszukuje identyfikatory. Kod
zostanie wykonane jednorazowo po zainicjowaniu klasy. jeśli klasa zostanie kiedykolwiek wyładowana z pamięci i
i załaduje ponownie stronę, zostanie on wykonany jeszcze raz.
Odwołania lokalne i globalne
Każdy argument przekazywany do metody natywnej i prawie każdy obiekt zwraca przez funkcję JNI to „lokalne odniesienie”. Oznacza to, że jest on prawidłowy w tagu czas trwania bieżącej metody natywnej w bieżącym wątku. Nawet jeśli obiekt będzie nadal działać po zastosowaniu metody natywnej odwołanie jest nieprawidłowe.
Dotyczy to wszystkich podklas klasy jobject
, w tym
jclass
, jstring
i jarray
.
Środowisko wykonawcze ostrzega o większości nadużyć związanych z plikami referencyjnymi, gdy rozszerzone JNI
– sprawdzanie jest włączone).
Jedynym sposobem na uzyskanie odniesień nielokalnych jest użycie funkcji
NewGlobalRef
i NewWeakGlobalRef
.
Jeśli chcesz przechowywać plik referencyjny przez dłuższy okres, musisz użyć
„globalnie” odwołania. Funkcja NewGlobalRef
przyjmuje
odniesienie lokalne jako argument i zwraca wartość globalną.
Globalny plik referencyjny jest zagwarantowany, że dopóki nie wywołasz
DeleteGlobalRef
Ten wzorzec jest zwykle używany podczas buforowania zwracanego obiektu jclass w pamięci podręcznej
z: FindClass
, np.
jclass localClass = env->FindClass("MyClass"); jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
Wszystkie metody JNI akceptują jako argumenty zarówno lokalne, jak i globalne odwołania.
Odwołania do tego samego obiektu mogą mieć różne wartości.
Na przykład wartości zwracane z kolejnych wywołań funkcji
Uprawnienia NewGlobalRef
w tym samym obiekcie mogą być inne.
Aby sprawdzić, czy dwa odwołania odnoszą się do tego samego obiektu,
musisz użyć funkcji IsSameObject
. Nigdy nie porównuj
odwołania z ==
w kodzie natywnym.
Jedną z konsekwencji jest to,
nie mogą zakładać, że odwołania do obiektów są stałe lub unikalne,
w kodzie natywnym. Wartość reprezentująca obiekt może być różna
z jednego wywołania metody do kolejnego. Możliwe jest, że dwie metody
różne obiekty mogą mieć tę samą wartość w kolejnych wywołaniach. Nie używać
jobject
wartości jako klucze.
Programiści muszą „nie nadmiernie przydzielać” i lokalne odniesienia. W praktyce oznacza to
W przypadku tworzenia dużej liczby lokalnych odwołań, na przykład wyświetlając
, zwolnij je ręcznie,
DeleteLocalRef
, zamiast pozwolić JNI zrobić to za Ciebie.
wymaga jedynie rezerwacji przedziałów
16 odniesień lokalnych. Jeśli potrzebujesz ich więcej, możesz je usuwać lub użyć
EnsureLocalCapacity
/PushLocalFrame
, by zarezerwować więcej.
Elementy jfieldID
i jmethodID
są nieprzezroczyste
a nie odwołania do obiektów i nie powinny być przekazywane do
NewGlobalRef
Nieprzetworzone dane
wskaźniki zwracane przez funkcje takie jak GetStringUTFChars
i GetByteArrayElements
też nie są obiektami. (mogą zostać zdane
między wątkami i mają zastosowanie do pasującego wywołania Release).
Jeden nietypowy przypadek zasługuje na oddzielną wzmiankę. Jeśli dołączysz reklamę natywną
w wątku AttachCurrentThread
, uruchamiany przez Ciebie kod
nigdy automatycznie nie zwalniają odnośników lokalnych, dopóki wątek nie zostanie odłączony. Dowolny lokalny
musisz usunąć je ręcznie. Ogólnie wszystkie reklamy natywne
który tworzy lokalne odniesienia w pętli, prawdopodobnie wymaga ręcznego
usunięcia.
Zachowaj ostrożność podczas korzystania z odwołań globalnych. Odniesienia globalne bywają nie do uniknięcia, ale są trudne do debugowania i może powodować problemy z diagnostyką pamięci. Jeśli inne ustawienia są takie same, i z mniejszą liczbą globalnych odniesień.
Ciągi znaków UTF-8 i UTF-16
W języku programowania Java zastosowano kodowanie UTF-16. Dla wygody JNI udostępnia metody, które współpracują z zmodyfikowane UTF-8. zmodyfikowane kodowanie jest przydatne w przypadku kodu C, ponieważ koduje \u0000 jako 0xc0 0x80 zamiast 0x00. Zaletą jest to, że można liczyć na ciągi znaków w stylu C z zerowym zakończeniem. odpowiedni do zastosowania ze standardowymi funkcjami ciągu znaków libc. Wadą jest to, że nie można danych w formacie UTF-8 do JNI i oczekuje, że będą one działać prawidłowo.
Aby wyświetlić ciąg znaków String
w formacie UTF-16, użyj formatu GetStringChars
.
Pamiętaj, że ciągi UTF-16 nie są zakończone od zera, a ciąg znaków \u0000 jest dozwolony.
więc pamiętaj o długości ciągu znaków i wskaźniku jchar.
Nie zapomnij Release
ciągów znaków Get
.
funkcje ciągu znaków zwracają jchar*
lub jbyte*
, które
to wskaźniki w stylu C do danych podstawowych, a nie lokalne odwołania. Ta
są ważne do momentu wywołania funkcji Release
, co oznacza, że nie są
po powrocie metody natywnej.
Dane przekazywane do NewStringUTF muszą mieć zmodyfikowany format UTF-8. O
typowym błędem jest odczyt danych znaków z pliku lub strumienia sieciowego
i przekazuje ją NewStringUTF
bez filtrowania.
Jeśli nie wiesz, że dane mają format MUTF-8 (lub 7-bitowy kod ASCII, który jest zgodnym podzbiorem),
musisz usunąć nieprawidłowe znaki lub przekonwertować je na prawidłowy zmodyfikowany format UTF-8.
Jeśli tego nie zrobisz, konwersja UTF-16 może przynieść nieoczekiwane rezultaty.
CheckJNI – domyślnie włączone w przypadku emulatorów – skanuje ciągi znaków.
i przerywa maszynę wirtualną, jeśli otrzyma nieprawidłowe dane wejściowe.
Przed Androidem 8 działanie ciągów UTF-16 było zwykle szybsze, tak jak w przypadku Androida.
nie wymagały kopii w języku GetStringChars
, podczas gdy
Pole GetStringUTFChars
wymagało przydziału i konwersji na UTF-8.
Android 8 zmienił reprezentację String
na 8 bitów na znak
dla ciągów ASCII (aby zaoszczędzić pamięć) i zaczął używać
przeprowadzka
śmieciarek. Te funkcje znacznie ograniczają liczbę przypadków, w których ART
może wyświetlić wskaźnik do danych String
bez tworzenia kopii, nawet
za GetStringCritical
. Jeśli jednak większość ciągów jest przetwarzanych przez kod
są krótkie, w większości przypadków można uniknąć alokacji i przydzielania
za pomocą bufora przydzielonego do stosu i GetStringRegion
lub
GetStringUTFRegion
Na przykład:
constexpr size_t kStackBufferSize = 64u; jchar stack_buffer[kStackBufferSize]; std::unique_ptr<jchar[]> heap_buffer; jchar* buffer = stack_buffer; jsize length = env->GetStringLength(str); if (length > kStackBufferSize) { heap_buffer.reset(new jchar[length]); buffer = heap_buffer.get(); } env->GetStringRegion(str, 0, length, buffer); process_data(buffer, length);
Tablice podstawowe
JNI udostępnia funkcje dostępu do zawartości obiektów tablicy. Dostęp do tablic obiektów wymaga dostępu do jednego wpisu naraz, a tablice elementy podstawowe można odczytywać i zapisywać bezpośrednio tak, jakby były zadeklarowane w C.
Aby maksymalnie zwiększyć wydajność interfejsu bez ograniczania
wdrożenie maszyny wirtualnej, Get<PrimitiveType>ArrayElements
rodzina wywołań pozwala środowisku wykonawczym zwrócić wskaźnik do rzeczywistych elementów lub
przydzielić część pamięci i utworzyć kopię. Tak czy inaczej, został zwrócony nieprzetworzony wskaźnik
jest ważna do momentu odpowiedniego wywołania funkcji Release
(co oznacza, że jeśli dane nie zostały skopiowane, obiekt tablicy
zostaną przypięte i nie można ich przenieść w ramach kompresowania sterty).
Musisz wykonać Release
w każdej tablicy Get
. Ponadto, jeśli Get
nie udało się wywołać, musisz upewnić się, że Twój kod nie próbuje Release
wartości NULL
później.
Możesz sprawdzić, czy dane zostały skopiowane, przekazując w pole
wskaźnik (nie NULL) dla argumentu isCopy
. Rzadko
przydatne.
Wywołanie Release
przyjmuje argument mode
, który może
mają jedną z trzech wartości. Działania wykonywane przez środowisko wykonawcze zależą od
czy zwrócił wskaźnik do rzeczywistych danych czy ich kopii:
0
- Rzeczywiste: obiekt tablicy jest odpięty.
- Kopiuj: dane są kopiowane. Bufor z kopią zostanie zwolniony.
JNI_COMMIT
- Rzeczywista: nic nie robi.
- Kopiuj: dane są kopiowane. Bufor z kopią nie została zwolniona.
JNI_ABORT
- Rzeczywiste: obiekt tablicy jest odpięty. Wcześniej zapisy nie są przerywane.
- Copy: bufor z kopią zostaje uwolniony; wszelkie wprowadzone w nim zmiany zostaną utracone.
Jednym z powodów sprawdzania flagi isCopy
jest sprawdzenie, czy
Musisz zadzwonić do: Release
, używając numeru JNI_COMMIT
po wprowadzeniu zmian w tablicy — jeśli naprzemiennie wykonujesz
zmian i wykonania kodu korzystającego z zawartości tablicy
w stanie
pomiń zatwierdzenie bez działania. Innym możliwym powodem sprawdzenia tej flagi jest
sprawną obsługę JNI_ABORT
. Możesz na przykład zaznaczyć,
uzyskać tablicę, zmodyfikować ją na miejscu, przekazać fragmenty do innych funkcji oraz
i odrzuć zmiany. Jeśli wiesz, że JNI tworzy nową kopię pliku
nie ma potrzeby tworzenia kolejnych kopiuj. Jeśli JNI ma stan pozytywny
oryginału, musisz samodzielnie utworzyć jego kopię.
Częstym błędem jest założenie, że można pominąć wywołanie Release
, jeśli:
Wartość *isCopy
ma wartość fałsz. To nieprawda. Jeśli brak było bufora kopiowania
przydzielona, pierwotna pamięć musi być przypięta i nie może zostać przeniesiona
na śmieci.
Pamiętaj też, że flaga JNI_COMMIT
nie zwalnia tablicy,
i trzeba będzie ponownie wywołać funkcję Release
, używając innej flagi
w końcu.
Wywołania regionalne
Możesz też korzystać z alternatywnych połączeń, takich jak Get<Type>ArrayElements
i GetStringChars
. Mogą być przydatne,
gdy chcesz
tylko kopiować dane. Weź pod uwagę następujące kwestie:
jbyte* data = env->GetByteArrayElements(array, NULL); if (data != NULL) { memcpy(buffer, data, len); env->ReleaseByteArrayElements(array, data, JNI_ABORT); }
Ta opcja pobiera tablicę i kopiuje pierwszy bajt (len
)
elementów, a potem uwalnia tablicę. W zależności od
implementacja, wywołanie Get
przypnie lub skopiuje tablicę
treści.
Kod kopiuje dane (prawdopodobnie drugi raz) i wywołuje funkcję Release
. W tym przypadku
JNI_ABORT
gwarantuje, że nie ma szans na utworzenie trzeciej kopii.
To samo można osiągnąć prościej:
env->GetByteArrayRegion(array, 0, len, buffer);
Ma to kilka zalet:
- Wymaga 1 wywołania JNI zamiast 2, co zmniejsza narzut.
- Nie wymaga przypinania ani tworzenia dodatkowych kopii danych.
- Zmniejsza ryzyko błędu programisty – brak ryzyka zapomnienia
aby wywołać
Release
, gdy coś się nie uda.
W podobny sposób można użyć wywołania Set<Type>ArrayRegion
aby skopiować dane do tablicy, a GetStringRegion
lub
GetStringUTFRegion
, aby skopiować znaki z
String
Wyjątki
Nie możesz wywoływać większości funkcji JNI podczas oczekiwania na wyjątek.
Twój kod powinien zauważyć wyjątek (przez zwracaną wartość funkcji
ExceptionCheck
lub ExceptionOccurred
) i zwrot,
lub usuń wyjątek i obsługuj go.
Jedynym wyjątkiem są funkcje JNI, które można wywołać oczekujące to:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
Wiele wywołań JNI może zgłaszać wyjątek, ale często zapewnia prostszy sposób
na ewentualne błędy. Jeśli na przykład NewString
zwraca
nie ma wartości NULL, nie musisz sprawdzać wyjątku. Jeśli jednak
wywołasz metodę (za pomocą funkcji takiej jak CallObjectMethod
),
zawsze należy sprawdzać wyjątek, ponieważ zwrócona wartość nie jest
będzie prawidłowa, jeśli zostanie zgłoszony wyjątek.
Pamiętaj, że wyjątki zgłoszone przez kod zarządzany nie powodują wycofania stosu natywnego
ramki. (Wyjątki od C++, zasadniczo odradzane w przypadku Androida, nie mogą być
poza granicą przejścia JNI z kodu C++ do kodu zarządzanego).
Instrukcje dotyczące JNI Throw
i ThrowNew
ustaw wskaźnik wyjątku w bieżącym wątku. Po powrocie do konta zarządzanego
z kodu natywnego, wyjątek zostanie odnotowany i odpowiednio obsłużony.
Kod natywny może „przechwytywać” wyjątek, wywołając ExceptionCheck
lub
ExceptionOccurred
i usuń ją za pomocą
ExceptionClear
Jak zwykle,
odrzucanie wyjątków bez zajmowania się nimi może prowadzić do problemów.
Nie ma wbudowanych funkcji do manipulowania obiektem Throwable
więc aby uzyskać ciąg wyjątku,
znajdź klasę Throwable
, wyszukaj identyfikator metody dla
getMessage "()Ljava/lang/String;"
, wywołaj go, a jeśli wynik
nie jest wartością NULL, użyj GetStringUTFChars
, aby uzyskać coś,
do printf(3)
lub równoważnego elementu.
Sprawdzanie rozszerzone
JNI sprawdza bardzo mało błędów. Błędy zazwyczaj prowadzą do awarii. Android oferuje również tryb CheckJNI, w którym wskaźniki tabel funkcji JavaVM i JNIEnv są przełączane na tabele funkcji, które przeprowadzają rozszerzoną serię testów przed wywołaniem standardowej implementacji.
Dodatkowe kontrole obejmują:
- Tablice: próba przydzielenie tablicy o ujemnym rozmiarze.
- Złe wskaźniki: błędne przekazanie wartości jarray/jclass/jobject/jstring do wywołania JNI lub przekazanie wskaźnika NULL do wywołania JNI z argumentem niezawierającym wartości null.
- Nazwy klas: przekazywanie do wywołania JNI wszystkiego poza stylem „java/lang/String” nazwy klasy.
- Wywołania krytyczne: nawiązanie połączenia JNI między „krytycznym” pobraniem a odpowiadającą mu wersją.
- Bezpośrednie bufory ByteBuffers: przekazywanie nieprawidłowych argumentów do funkcji
NewDirectByteBuffer
. - Wyjątki: wykonywanie wywołania JNI w czasie oczekiwania na wyjątek.
- JNIEnv*s: użycie JNIEnv* z niewłaściwego wątku.
- jfieldID: użycie NULL jfieldID lub użycie jfieldID do nadania pola wartości nieprawidłowego typu (np. próby przypisania pola StringBuilder do pola String), użycie identyfikatora jfieldID dla pola statycznego do ustawienia pola instancji (lub odwrotne), albo użycie jfieldID z jednej klasy z wystąpieniami innej klasy.
- jmethodID: użycie niewłaściwego rodzaju identyfikatora jmethodID przy wywołaniu JNI
Call*Method
: nieprawidłowy typ zwracania, niezgodność statyczna/niestatyczna, nieprawidłowy typ dla argumentu „this” (w przypadku wywołań niestatycznych) lub nieprawidłowa klasa (w przypadku wywołań statycznych). - Pliki referencyjne: użyto
DeleteGlobalRef
/DeleteLocalRef
w niewłaściwym rodzaju pliku referencyjnego. - Tryby wersji: przekazywanie niewłaściwego trybu zwalniania do wywołania zwalniania (innego niż
0
,JNI_ABORT
lubJNI_COMMIT
). - Bezpieczeństwo podczas pisania – zwracanie niezgodnego typu z metody natywnej (zwracanie obiektu StringBuilder z metody zadeklarowanej w celu zwrócenia ciągu znaków).
- UTF-8: przekazanie do wywołania JNI nieprawidłowej sekwencji bajtowej zmodyfikowanej UTF-8.
(Dostępność metod i pól nadal nie jest zaznaczona – ograniczenia dostępu nie mają zastosowania do kodu natywnego).
Możesz włączyć CheckJNI na kilka sposobów.
Jeśli używasz emulatora, funkcja CheckJNI jest domyślnie włączona.
Jeśli masz urządzenie z dostępem do roota, możesz użyć tej sekwencji poleceń, aby ponownie uruchomić środowisko wykonawcze z włączoną funkcją CheckJNI:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
W każdym z tych przypadków po uruchomieniu środowiska wykonawczego w danych wyjściowych logcat pojawi się coś takiego:
D AndroidRuntime: CheckJNI is ON
Jeśli masz zwykłe urządzenie, możesz użyć tego polecenia:
adb shell setprop debug.checkjni 1
Nie będzie to miało wpływu na już uruchomione aplikacje, ale wszystkie aplikacje uruchomione od tego momentu będą miały włączoną funkcję CheckJNI. (Zmień właściwość na dowolną inną wartość lub zrestartowanie urządzenia ponownie wyłączy CheckJNI). W takim przypadku przy następnym uruchomieniu aplikacji w danych wyjściowych logcat zobaczysz coś takiego:
D Late-enabling CheckJNI
Możesz też ustawić atrybut android:debuggable
w pliku manifestu aplikacji na
włącz CheckJNI tylko dla swojej aplikacji. Pamiętaj, że narzędzia do kompilacji na Androida robią to automatycznie w przypadku
określonych typów kompilacji.
Biblioteki natywne
Możesz wczytać kod natywny z bibliotek udostępnionych za pomocą
System.loadLibrary
W praktyce starsze wersje Androida zawierały błędy w Menedżerze pakietów, które powodowały instalację i aktualizacja bibliotek natywnych. ReLinker projekt oferuje sposoby obejścia tego i innych problemów z wczytywaniem biblioteki natywnej.
Wywołaj metodę System.loadLibrary
(lub ReLinker.loadLibrary
) z klasy statycznej
inicjator. Argument to: „bezdekoracja” nazwa biblioteki,
więc aby wczytać interfejs libfubar.so
, musisz przekazać w "fubar"
.
Jeśli masz tylko jedną klasę z metodami natywnymi, wywołanie funkcji
System.loadLibrary
, aby był statycznym inicjatorem dla tej klasy. W przeciwnym razie
chcesz nawiązać połączenie z numeru Application
, aby wiedzieć, że biblioteka jest zawsze wczytywana,
i zawsze wczytywane z wyprzedzeniem.
Środowisko wykonawcze może znaleźć metody natywne na 2 sposoby. Możesz wprost
zarejestruj je w usłudze RegisterNatives
lub pozwól na dynamiczne wyszukiwanie w środowisku wykonawczym.
dzięki dlsym
. Zaletą usługi RegisterNatives
jest to, że użytkownicy od razu widzą
sprawdzania, czy te symbole istnieją. Dodatkowo możesz mieć mniejsze i szybsze biblioteki współdzielone,
eksportuję wszystko oprócz JNI_OnLoad
. Zaletą tego, że środowisko wykonawcze ma możliwość wykrywania
polega na tym, że pisanie kodu wymaga nieco mniej.
Aby użyć aplikacji RegisterNatives
:
- Podaj funkcję
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
. - W narzędziu
JNI_OnLoad
zarejestruj wszystkie metody natywne za pomocą metodyRegisterNatives
. - Utwórz przy użyciu
-fvisibility=hidden
, tak aby tylkoJNI_OnLoad
zostanie wyeksportowany z biblioteki. Dzięki temu kod jest szybszy i mniejszy oraz pozwala uniknąć potencjalnych problemów kolizje z innymi bibliotekami wczytanymi do aplikacji (ale tworzy to mniej przydatne zrzuty stosu); w przypadku awarii aplikacji w kodzie natywnym).
Statyczny inicjator powinien wyglądać następująco:
Kotlin
companion object { init { System.loadLibrary("fubar") } }
Java
static { System.loadLibrary("fubar"); }
Funkcja JNI_OnLoad
powinna wyglądać mniej więcej tak, jeśli
napisane w C++:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // Find your class. JNI_OnLoad is called from the correct class loader context for this to work. jclass c = env->FindClass("com/example/app/package/MyClass"); if (c == nullptr) return JNI_ERR; // Register your class' native methods. static const JNINativeMethod methods[] = { {"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)}, {"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)}, }; int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod)); if (rc != JNI_OK) return rc; return JNI_VERSION_1_6; }
Aby zamiast tego korzystać z funkcji „odkrywania” metod natywnych, musisz nazwać je w określony sposób (zobacz specyfikacja JNI ). Oznacza to, że jeśli podpis metody jest nieprawidłowy, nie będziesz wiedzieć o nim do czasu przy pierwszym wywołaniu metody.
Wszystkie wywołania funkcji FindClass
nawiązane z adresu JNI_OnLoad
będą zamykać zajęcia w
w narzędziu do wczytywania zajęć, który został użyty do wczytania biblioteki udostępnionej. W przypadku połączenia z innego urządzenia
w kontekstach, FindClass
używa programu wczytującego klas powiązanego z metodą znajdującą się
stos Java, a jeśli takiego nie ma (wywołanie pochodzi z nowo dołączonego wątku natywnego).
Wykorzystuje „system”, . Moduł ładujący klas systemu nie wie o
zajęć, więc nie będziesz mieć możliwości wyszukiwania swoich zajęć z FindClass
w tych
i dodaje kontekst. Dzięki temu JNI_OnLoad
to wygodne miejsce do wyszukiwania i buforowania klas: raz
masz prawidłowy odniesienie globalne typu jclass
Wykorzystasz go z dowolnego dołączonego wątku.
Szybsze połączenia natywne dzięki funkcjom @FastNative
i @CriticalNative
Metody natywne można opatrzyć adnotacjami
@FastNative
lub
@CriticalNative
(ale nie obu) w celu przyspieszenia przejścia między kodem zarządzanym a natywnym. Jednak te adnotacje
wiążą się z pewnymi zmianami w działaniu, które należy dokładnie przemyśleć przed użyciem. Choć
krótko wspomnij o tych zmianach poniżej. Szczegóły znajdziesz w dokumentacji.
Adnotację @CriticalNative
można stosować tylko do metod natywnych, które nie korzystają z innych metod
używają obiektów zarządzanych (w parametrach lub zwracanych wartościach lub jako niejawnie zdefiniowanej klasy this
) i to
zmienia interfejs ABI przejścia JNI. Implementacja natywna musi wykluczać
Parametry JNIEnv
i jclass
z podpisu funkcji.
Podczas wykonywania metody @FastNative
lub @CriticalNative
funkcja czyszczenia pamięci
kolekcja nie może zawiesić wątku w związku z wykonaniem niezbędnych zadań i może zostać zablokowana. Nie używaj tych
adnotacji do długotrwałych metod, w tym zwykle szybkich, ale zwykle nieograniczonych metod.
W szczególności kod nie powinien wykonywać istotnych operacji wejścia-wyjścia ani nakładać blokad natywnych, które
może trwać bardzo długo.
Te adnotacje zostały wdrożone do użytku systemowego od
Androida 8
i stały się publicznymi testowanymi przez CTS
API w Androidzie 14. Te optymalizacje prawdopodobnie sprawdzą się również na urządzeniach z Androidem 8–13 (ale
bez silnych gwarancji CTS), jednak dynamiczne wyszukiwanie metod natywnych jest obsługiwane tylko
Androida 12 lub nowszego, wyraźna rejestracja w JNI RegisterNatives
jest surowo wymagana
na urządzenia z Androidem w wersji 8–11. Te adnotacje są ignorowane na Androidzie 7, niezgodność interfejsu ABI
dla @CriticalNative
spowodowałoby organizowanie niewłaściwego argumentu i prawdopodobnych awarii.
W przypadku metod mających kluczowe znaczenie dla wydajności, które wymagają tych adnotacji, zdecydowanie zalecamy
bezpośrednio zarejestrować metody w JNI RegisterNatives
zamiast polegać na
„odkrywanie” oparte na nazwach metod natywnych. Aby uzyskać optymalną wydajność uruchamiania aplikacji, zalecamy
, aby uwzględnić elementy wywołujące metody @FastNative
lub @CriticalNative
w funkcji
profilu odniesienia. Od Androida 12
wywołanie metody natywnej @CriticalNative
ze skompilowanej metody zarządzanej jest prawie tak
tani jako niewbudowane wywołanie w C/C++, o ile wszystkie argumenty mieszczą się w rejestrach (na przykład do
8 całek i maksymalnie 8 argumentów zmiennoprzecinkowych w arm64).
Czasami najlepiej jest podzielić metodę natywną na dwie, czyli bardzo szybką, i kolejnej, która zajmuje się powolnymi sprawami. Na przykład:
Kotlin
fun writeInt(nativeHandle: Long, value: Int) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value) } } @CriticalNative external fun nativeTryBufferedWriteInt(nativeHandle: Long, value: Int): Boolean external fun nativeWriteInt(nativeHandle: Long, value: Int)
Java
void writeInt(long nativeHandle, int value) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value); } } @CriticalNative static native boolean nativeTryBufferedWriteInt(long nativeHandle, int value); static native void nativeWriteInt(long nativeHandle, int value);
Uwagi na temat wersji 64-bitowej
Aby obsługiwać architektury korzystające z 64-bitowych wskaźników, użyj pola long
zamiast
int
podczas przechowywania wskaźnika do natywnej struktury w polu Java.
Nieobsługiwane funkcje/zgodność wsteczna
Obsługiwane są wszystkie funkcje JNI 1.6 z tymi wyjątkami:
- Nie zaimplementowano elementu
DefineClass
. Android nie używa kod bajtowy Java lub pliki klas, więc przekazywanie danych klasy binarnej nie działa.
Aby uzyskać zgodność wsteczną ze starszymi wersjami Androida, może być konieczne pamiętaj o tych kwestiach:
- Dynamiczne wyszukiwanie funkcji natywnych
Do Androida 2.0 (Eclair) znak „$” znak jest niepoprawny przekonwertowano na „_00024” podczas wyszukiwania nazw metod. Pracuje wymaga wyraźnej rejestracji lub przeniesienia metod natywnych spoza klas wewnętrznych.
- Odłączanie wątków
Do Androida 2.0 (Eclair) nie można było używać interfejsu
pthread_key_create
destruktora, aby uniknąć „odłączenia wątku przed wyjdź” sprawdzić. (środowisko wykonawcze korzysta również z funkcji destrukcji kluczy pthread, i wyścig o to, kto zdobędzie pierwsze nazwisko). - Słabe globalne odwołania
Do Androida 2.2 (Froyo) słabe globalne odniesienia nie były stosowane. Starsze wersje będą zdecydowanie odrzucać próby ich użycia. Za pomocą wersji platformy Androida do testowania pod kątem obsługi.
Do Androida 4.0 (Ice Cream Sandwich) słabe globalne odniesienia mogły być mogą być przekazywane do
NewLocalRef
,NewGlobalRef
iDeleteWeakGlobalRef
(W specyfikacji zdecydowanie zalecamy mogą tworzyć silne odniesienia do słabych globalnych rynków, zanim to zrobią więc to nie powinno być w ogóle ograniczające).Począwszy od Androida 4.0 (Ice Cream Sandwich) słabe globalne odniesienia mogą jak wszystkie inne odniesienia JNI.
- Lokalne materiały referencyjne
Do Androida 4.0 (Ice Cream Sandwich) lokalne odniesienia były a nawet bezpośrednich wskaźników. Ice Cream Sandwich dodał reklamę pośrednią niezbędną do zapewnienia lepszej usługi zbierania odpadów, ale oznacza to, błędów JNI nie można wykryć w starszych wersjach. Zobacz Więcej informacji o zmianach referencyjnych JNI w ICS.
Na urządzeniach z Androidem w wersjach starszych niż 8.0 liczba lokalnych odwołań jest ograniczona do limitu określonego dla wersji. Począwszy od Androida 8.0, Android obsługuje nieograniczoną liczbę lokalnych odwołań.
- Określanie typu pliku referencyjnego za pomocą parametru
GetObjectRefType
Do Androida 4.0 (Ice Cream Sandwich) w wyniku używania za pomocą bezpośrednich wskaźników (patrz wyżej), nie dało się wdrożyć
GetObjectRefType
. Zamiast tego zastosowaliśmy metodę heurystyczną, przeanalizował tabelę słabych globalnych światów, argumenty, miejscowych tabeli globalnych i tabeli globalnych w tej kolejności. po pierwszym odnalezieniu Twojego bezpośredni wskaźnik, wskaże, że Twój plik referencyjny jest typu być może. Oznaczało to na przykład, że jeśli na przykład dzwoniłeś(-aś) doGetObjectRefType
w globalnym spotkaniu Jclass, taką samą jak argument jclass przekazywany jako niejawny argument do statycznego parametru natywną, otrzymaszJNILocalRefType
zamiastJNIGlobalRefType
@FastNative
i@CriticalNative
Do Androida 7 te adnotacje optymalizacyjne były ignorowane. interfejs ABI niezgodność wartości
@CriticalNative
prowadzi do błędnego argumentu masaż i prawdopodobne awarie.Dynamiczne wyszukiwanie funkcji natywnych w interfejsach
@FastNative
oraz W Androidzie 8–10 nie zaimplementowano@CriticalNative
metod i zawiera znane błędy w Androidzie 11. Korzystanie z tych optymalizacji bez wyraźna rejestracja w JNIRegisterNatives
prawdopodobnie mogą prowadzić do awarii na urządzeniach z Androidem 8–11.FindClass
rzucaClassNotFoundException
Aby zapewnić zgodność wsteczną, Android zgłasza
ClassNotFoundException
zamiastNoClassDefFoundError
, gdy klasa nie zostanie znaleziona przezFindClass
To zachowanie jest zgodne z interfejsem Javy Referion API.Class.forName(name)
Najczęstsze pytania: dlaczego otrzymuję UnsatisfiedLinkError
?
Podczas pracy z kodem natywnym często zdarzają się błędy podobne do tych:
java.lang.UnsatisfiedLinkError: Library foo not found
W niektórych przypadkach może to oznaczać, że znajduje się tu informacja – biblioteka nie została znaleziona. W
w innych przypadkach biblioteka istnieje, ale dlopen(3)
nie mogła jej otworzyć,
szczegóły błędu można znaleźć w wiadomości ze szczegółami wyjątku.
Najczęstsze przyczyny błędu „Nie znaleziono biblioteki” wyjątki:
- Biblioteka nie istnieje lub jest niedostępna dla aplikacji. Używaj
adb shell ls -l <path>
, aby sprawdzić obecność i uprawnieniach. - Biblioteka nie została zbudowana za pomocą NDK. Może to spowodować zależności od funkcji lub bibliotek, które nie istnieją na urządzeniu.
Kolejna klasa błędów (UnsatisfiedLinkError
) wygląda tak:
java.lang.UnsatisfiedLinkError: myfunc at Foo.myfunc(Native Method) at Foo.main(Foo.java:10)
W narzędziu Logcat zobaczysz:
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
Oznacza to, że środowisko wykonawcze próbowało znaleźć metodę dopasowania, ale niepowodzenie. Oto kilka najczęstszych przyczyn:
- Nie można wczytać biblioteki. Sprawdź dane wyjściowe logcat pod kątem komunikaty o wczytywaniu biblioteki.
- Nie można znaleźć metody z powodu niezgodności nazwy lub podpisu. Ten
jest często spowodowana przez:
- brak zadeklarowania funkcji w języku C++ w przypadku leniwego wyszukiwania za pomocą metody;
z parametrem
extern "C"
i odpowiednim widoczność (JNIEXPORT
). Pamiętaj, że przed makro JNIEXPORT jest nieprawidłowe, więc użycie nowego GCC z Staryjni.h
nie będzie działać. Możesz użyć usługiarm-eabi-nm
aby zobaczyć, jak wyglądają symbole w bibliotece; jeśli spojrzymy na zniekształcony (np._Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
) zamiastJava_Foo_myfunc
) lub jeśli typ symbolu to małe litery „t” zamiast wielkiego T, musisz i dostosować deklarację. - W przypadku wyraźnej rejestracji mogą wystąpić drobne błędy podczas wpisywania
podpis metody. Upewnij się, że informacje przekazywane do
jest zgodne z podpisem w pliku dziennika.
Pamiętaj, że „B” to
byte
i „Z” jestboolean
. Komponenty nazwy klas w podpisach zaczynają się od litery „L” i kończą się znakiem „;”, użyj znaku „/” aby oddzielić nazwy pakietów/klas, a następnie użyć „$” aby rozdzielić nazw klas wewnętrznych (np.Ljava/util/Map$Entry;
).
- brak zadeklarowania funkcji w języku C++ w przypadku leniwego wyszukiwania za pomocą metody;
z parametrem
Użycie javah
do automatycznego generowania nagłówków JNI może pomóc
aby uniknąć problemów.
Najczęstsze pytania: dlaczego FindClass
nie znalazł(a) moich zajęć?
(Większość tych porad dotyczy równie dobrego przypadku, jeśli nie uda się znaleźć metod
z polem GetMethodID
lub GetStaticMethodID
albo polami
przy użyciu GetFieldID
lub GetStaticFieldID
).
Sprawdź, czy ciąg nazwy klasy ma prawidłowy format. Zajęcia JNI
zaczynają się od nazwy pakietu i są oddzielone ukośnikami,
na przykład java/lang/String
. Jeśli szukasz klasy tablicy,
musisz zacząć od odpowiedniej liczby nawiasów kwadratowych
musi też opakowywać klasę znakiem „L” i „;”, a więc jednowymiarowa tablica
String
to [Ljava/lang/String;
.
Jeśli szukasz informacji o klasie wewnętrznej, użyj „$” zamiast „.”. Ogólnie rzecz biorąc,
użycie funkcji javap
w pliku .class jest dobrym sposobem na sprawdzenie
wewnętrzną nazwę zajęć.
Jeśli włączysz zmniejszanie kodu, upewnij się, określić, który kod chcesz zachować. Konfiguruję bo w przeciwnym razie narzędzie zmniejszające kod może usunąć klasy, metody lub pola, które są używane tylko z JNI.
Jeśli nazwa zajęć wygląda poprawnie, możliwe, że korzystasz z programu wczytującego klasy
Google Cloud. FindClass
chce rozpocząć wyszukiwanie zajęć w
program ładujący klasy powiązany z kodem. Analizuje on stos wywołań,
który będzie wyglądać mniej więcej tak:
Foo.myfunc(Native Method) Foo.main(Foo.java:10)
Metoda znajdująca się na samej górze to Foo.myfunc
. FindClass
znajduje obiekt ClassLoader
powiązany z tabelą Foo
i używa go.
Zwykle robi to to, na co masz ochotę. Możesz mieć kłopoty, jeśli będziesz
samodzielnie utwórz wątek (na przykład wywołając pthread_create
i załączając ją za pomocą elementu AttachCurrentThread
). Już jest.
nie są ramkami stosu z Twojej aplikacji.
Jeśli wywołasz funkcję FindClass
z tego wątku,
Oprogramowanie JavaVM uruchomi się w „systemie” program wczytujący klasy zamiast powiązanego
z aplikacją, więc próby znalezienia klas dla danej aplikacji zakończą się niepowodzeniem.
Oto kilka sposobów, aby obejść ten problem:
- Przeprowadź wyszukiwanie
FindClass
raz – zaJNI_OnLoad
i zapisz w pamięci podręcznej odwołania do klas na później i ich używanie. Wszystkie wywołaniaFindClass
w ramach wykonywaniaJNI_OnLoad
będzie używać programu wczytującego klasy powiązanego z funkcję o nazwieSystem.loadLibrary
(to jest specjalną regułę, aby ułatwić inicjowanie biblioteki). Jeśli kod aplikacji wczytuje bibliotekę,FindClass
będzie używać odpowiedniego programu wczytującego klasy. - Przekaż instancję klasy do funkcji, które wymagają
przez zadeklarowanie metody natywnej dla argumentu Class,
i przekazanie
Foo.class
do źródła. - Umieść w pamięci podręcznej odwołanie do obiektu
ClassLoader
i wykonywać bezpośrednie połączenia (loadClass
). Wymaga to trochę wysiłku.
Najczęstsze pytania: jak udostępnić nieprzetworzone dane za pomocą kodu natywnego?
Może się zdarzyć, że będziesz potrzebować dostępu do dużej bufor nieprzetworzonych danych z kodu zarządzanego i natywnego. Typowe przykłady obejmują manipulowanie mapami bitowymi lub próbkami dźwięku. Dostępne są 2 podstawowych metod.
Dane możesz przechowywać w: byte[]
. Pozwala to szybko
za pomocą kodu zarządzanego. Jednakże jeśli chodzi o środowisko natywne,
dostęp do danych bez konieczności ich kopiowania. W
niektóre implementacje, GetByteArrayElements
i
GetPrimitiveArrayCritical
zwróci rzeczywiste wskaźniki do funkcji
nieprzetworzone dane w zarządzanej stercie, a w innych przydzielają bufor
na stercie natywnej i skopiuj dane.
Alternatywą jest przechowywanie danych w bezpośrednim buforze bajtów. Te
można utworzyć za pomocą java.nio.ByteBuffer.allocateDirect
lub
funkcji JNI NewDirectByteBuffer
. W przeciwieństwie do zwykłych
bufory bajtów, pamięć nie jest przydzielana na zarządzanej stercie i może
będzie zawsze dostępny bezpośrednio z kodu natywnego (pobierz adres
korzystając z GetDirectBufferAddress
). W zależności od tego, jak bezpośrednia jest
wdrożony dostęp do bufora bajtów i dostęp do danych z kodu zarządzanego
może być bardzo wolna.
Ich wybór zależy od 2 czynników:
- Czy większość danych jest uzyskiwana za pomocą kodu napisanego w Javie? czy w C/C++?
- Jeśli dane będą w końcu przekazywane do systemowego interfejsu API, jaki formularz
musi znajdować się w środku? (Jeśli na przykład dane zostaną później przekazane do
funkcja, która przyjmuje bajt[], wykonując przetwarzanie w bezpośrednim
ByteBuffer
może nie być rozsądny).
Jeśli nie uda się wyłonić jednoznacznego zwycięzcy, użyj bezpośredniego bufora bajtów. Pomoc dla nich jest wbudowana w JNI, a jego wydajność powinna poprawić się w kolejnych wersjach.