Wskazówki dotyczące JNI

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 lub JNI_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ą metody RegisterNatives.
  • Utwórz przy użyciu -fvisibility=hidden, tak aby tylko JNI_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 i DeleteWeakGlobalRef (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ś) do GetObjectRefType w globalnym spotkaniu Jclass, taką samą jak argument jclass przekazywany jako niejawny argument do statycznego parametru natywną, otrzymasz JNILocalRefType zamiast JNIGlobalRefType

  • @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 JNI RegisterNatives prawdopodobnie mogą prowadzić do awarii na urządzeniach z Androidem 8–11.

  • FindClass rzuca ClassNotFoundException

    Aby zapewnić zgodność wsteczną, Android zgłasza ClassNotFoundException zamiast NoClassDefFoundError, gdy klasa nie zostanie znaleziona przez FindClass 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 Stary jni.h nie będzie działać. Możesz użyć usługi arm-eabi-nm aby zobaczyć, jak wyglądają symbole w bibliotece; jeśli spojrzymy na zniekształcony (np. _Z15Java_Foo_myfuncP7_JNIEnvP7_jclass) zamiast Java_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” jest boolean. 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;).

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 – za JNI_OnLoad i zapisz w pamięci podręcznej odwołania do klas na później i ich używanie. Wszystkie wywołania FindClass w ramach wykonywania JNI_OnLoad będzie używać programu wczytującego klasy powiązanego z funkcję o nazwie System.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:

  1. Czy większość danych jest uzyskiwana za pomocą kodu napisanego w Javie? czy w C/C++?
  2. 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.