JNI-Tipps

JNI ist das native Java-Interface. Es definiert eine Möglichkeit für den Bytecode, den Android aus Verwalteter Code (in den Programmiersprachen Java oder Kotlin) für die Interaktion mit nativem Code (in C/C++ geschrieben). JNI ist anbieterneutral und unterstützt das Laden von Code aus dynamischen freigegebenen Elementen. und zwar mitunter umständlich.

Hinweis:Da Android Kotlin in ART-kompatiblen Bytecode kompiliert, ähnlich wie bei der Programmiersprache Java, können Sie die Anweisungen auf dieser Seite sowohl auf Kotlin- und Java-Programmiersprachen im Hinblick auf die JNI-Architektur und die damit verbundenen Kosten zu testen. Weitere Informationen finden Sie unter Kotlin und Android

Wenn Sie noch nicht damit vertraut sind, lesen Sie die Spezifikation für native Java-Schnittstellen wie JNI funktioniert und welche Funktionen verfügbar sind. Einige auf der Benutzeroberfläche nicht sofort damit Sie die nächsten Abschnitte praktisch finden können.

Um globale JNI-Referenzen zu durchsuchen und zu sehen, wo globale JNI-Referenzen erstellt und gelöscht werden, verwenden Sie Ansicht JNI-Heap im Memory Profiler in Android Studio 3.2 und höher.

Allgemeine Tipps

Versuchen Sie, den Platzbedarf Ihrer JNI-Schicht zu minimieren. Hier sind mehrere Dimensionen zu berücksichtigen. Ihre JNI-Lösung sollte diese Richtlinien befolgen (unten nach Wichtigkeit sortiert, beginnend mit dem wichtigsten):

  • Marshalling von Ressourcen auf der JNI-Ebene minimieren. Marshalling über die JNI-Schicht mit relativ geringen Kosten verbunden ist. Versuchen Sie, eine Schnittstelle zu entwerfen, Daten, die Sie marshallen müssen, und die Häufigkeit, mit der Sie Daten marshallen müssen.
  • Die asynchrone Kommunikation zwischen Code, der in einer verwalteten Programmierung geschrieben ist, wird vermieden. Sprache und Code in C++ schreiben, wenn möglich. Dadurch wird die Wartung Ihrer JNI-Schnittstelle vereinfacht. In der Regel können Sie asynchrone Aktualisierungen der Benutzeroberfläche, indem das asynchrone Update in derselben Sprache wie die UI belassen wird Anstatt beispielsweise zum Aufrufen einer C++-Funktion aus dem UI-Thread im Java-Code über JNI, ist es besser, um einen Callback zwischen zwei Threads in der Programmiersprache Java auszuführen, wobei einer davon einen blockierenden C++-Aufruf aus und benachrichtigt den UI-Thread, wenn der blockierende Aufruf abgeschlossen ist.
  • Minimieren Sie die Anzahl der Threads, die von JNI berühren oder berührt werden müssen. Wenn Sie Threadpools sowohl in den Sprachen Java als auch in C++ verwenden müssen, versuchen Sie, JNI beizubehalten. Kommunikation zwischen den Poolinhabern und nicht zwischen einzelnen Worker-Threads.
  • Verwenden Sie nur wenige leicht zu identifizierende C++- und Java-Quellen für Ihren Schnittstellencode. Standorte, um zukünftige Refaktorierungen zu erleichtern. Automatische JNI-Generierung verwenden Bibliothek nach Bedarf anpassen.

JavaVM und JNIEnv

JNI definiert zwei wichtige Datenstrukturen, „JavaVM“ und "JNIEnv". Beide sind im Wesentlichen auf Zeiger auf Funktionstabellen. (In der C++-Version sind es Klassen mit einer Zeiger auf eine Funktionstabelle und eine Member-Funktion für jede JNI-Funktion, die einen indirekten in der Tabelle.) Die JavaVM stellt die Aufrufschnittstelle bereit. Funktionen, mit denen Sie eine JavaVM erstellen und zerstören können. Theoretisch können Sie mehrere JavaVMs pro Prozess haben, aber Android lässt nur eine zu.

JNIEnv bietet die meisten JNI-Funktionen. Ihre nativen Funktionen erhalten alle eine JNIEnv als das erste Argument, mit Ausnahme der @CriticalNative-Methoden, Siehe schnellere native Aufrufe.

JNIEnv wird für den lokalen Thread-Speicher verwendet. Aus diesem Grund können Sie eine JNIEnv nicht zwischen Threads teilen. Wenn ein Code-Snippet keine andere Möglichkeit hat, seine JNIEnv abzurufen, sollten Sie die JavaVM und verwenden GetEnv, um die JNIEnv des Threads zu ermitteln. (Vorausgesetzt, es hat eine; siehe AttachCurrentThread unten.)

Die C-Deklarationen von JNIEnv und JavaVM unterscheiden sich von der C++- Deklarationen. Die "jni.h"-Include-Datei enthält verschiedene typedefs je nachdem, ob sie in C oder C++ enthalten sind. Daher ist es keine gute Idee, JNIEnv-Argumente in Headerdateien beider Sprachen aufnehmen. Anders ausgedrückt: Wenn Ihre Headerdatei erfordert #ifdef __cplusplus. Möglicherweise müssen Sie zusätzliche Schritte ausführen, verweist dieser Header auf JNIEnv.)

Threads

Alle Threads sind Linux-Threads, die vom Kernel geplant werden. In der Regel mit verwaltetem Code gestartet (Thread.start() verwendet), Sie können aber auch an anderer Stelle erstellt und dann an JavaVM angehängt werden. Für Beispiel: Eine Unterhaltung, die mit pthread_create() oder std::thread gestartet wurde kann mit AttachCurrentThread() oder AttachCurrentThreadAsDaemon()-Funktionen. Bis ein Thread angehängt ist, enthält sie keine JNIEnv-Datei und kann keine JNI-Aufrufe durchführen.

In der Regel ist es am besten, Thread.start() zu verwenden, um Threads zu erstellen, die in Java-Code ein. Auf diese Weise stellen Sie sicher, dass Sie über genügend Stack-Speicherplatz verfügen, in der richtigen ThreadGroup und Sie verwenden denselben ClassLoader wie Ihren Java-Code. Es ist auch einfacher, den Thread-Namen für das Debugging in Java festzulegen nativen Code (siehe pthread_setname_np(), wenn Sie ein pthread_t- oder thread_t und std::thread::native_handle(), falls Sie eine std::thread und möchten pthread_t.

Das Anhängen eines nativ erstellten Threads führt zu einem java.lang.Thread Objekt, das erstellt und zum "main" hinzugefügt werden soll ThreadGroup, sodass sie für den Debugger sichtbar ist. AttachCurrentThread() wird angerufen für einen bereits angehängten Thread ist ein No-Op.

Android sperrt keine Threads, die nativen Code ausführen. Wenn Automatische Speicherbereinigung läuft oder der Debugger hat eine Sperrung veranlasst -Anforderung verwendet, pausiert Android den Thread beim nächsten JNI-Aufruf.

Über JNI angehängte Threads müssen DetachCurrentThread() vor dem Beenden. Wenn die direkte Programmierung umständlich ist, kann mit pthread_key_create() einen Destruktor definieren -Funktion, die aufgerufen wird, bevor der Thread beendet wird, und rufe dort DetachCurrentThread() an. (Verwenden Sie dieses Schlüssel mit pthread_setspecific(), um die JNIEnv-Datei in Thread-local-storage; auf diese Weise in Ihren Zerstörer übergeben werden, das Argument.)

jclass, jmethodID und jfieldID

Wenn Sie über nativen Code auf das Feld eines Objekts zugreifen möchten, gehen Sie so vor:

  • Mit FindClass die Klassenobjektreferenz für die Klasse abrufen
  • Feld-ID für das Feld mit GetFieldID abrufen
  • Rufen Sie den Inhalt des Felds mit einem geeigneten Inhalt ab, z. B. GetIntField

Auf ähnliche Weise erhalten Sie zum Aufrufen einer Methode zuerst einen Klassenobjektverweis und dann eine Methoden-ID. Die IDs sind oft nur auf interne Laufzeitdatenstrukturen verweist. Für die Suche sind möglicherweise mehrere Zeichenfolgen erforderlich Vergleiche. Sobald sie jedoch vorliegen, wird der eigentliche Aufruf zum Abrufen des Felds oder zum Aufrufen der Methode sehr schnell.

Wenn Leistung wichtig ist, sollten die Werte einmal abgerufen und die Ergebnisse im Cache gespeichert werden. in Ihrem nativen Code. Da es ein Limit von einer JavaVM pro Prozess gibt, ist es sinnvoll, um diese Daten in einer statischen lokalen Struktur zu speichern.

Die Klassenreferenzen, Feld-IDs und Methoden-IDs sind garantiert gültig, bis die Klasse entladen wird. Klassen werden nur entladen, wenn für alle mit einem ClassLoader verknüpften Klassen eine automatische Speicherbereinigung möglich ist. was selten ist, aber bei Android nicht unmöglich ist. Beachten Sie jedoch, dass jclass ist eine Klassenreferenz und muss mit einem Aufruf geschützt werden. an NewGlobalRef (siehe nächster Abschnitt).

Wenn Sie die IDs beim Laden einer Klasse und automatisch noch einmal im Cache speichern möchten Wenn die Klasse jemals entladen und neu geladen wird, ist die richtige Methode zum Initialisieren ID besteht darin, der entsprechenden Klasse ein Code-Snippet hinzuzufügen, das wie folgt aussieht:

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();
    }

Erstellen Sie in Ihrem C/C++ Code eine nativeClassInit-Methode, mit der die ID-Suchen durchgeführt werden. Der Code wird einmal ausgeführt, wenn die Klasse initialisiert wird. Wenn der Kurs entladen wird und neu geladen, wird er noch einmal ausgeführt.

Lokale und globale Referenzen

Jedes Argument, das an eine native Methode übergeben wird, und fast jedes Objekt zurückgegeben von einer JNI-Funktion ist eine „lokale Referenz“. Das bedeutet, dass er für den Dauer der aktuellen nativen Methode im aktuellen Thread. Selbst wenn das Objekt selbst nach der nativen Methode weiter besteht. gibt, ist der Verweis nicht gültig.

Dies gilt für alle abgeleiteten Klassen von jobject, einschließlich jclass, jstring und jarray. (Die Laufzeit warnt Sie vor den meisten Referenzmissbrauch, wenn die JNI erweitert wird. Prüfungen aktiviert sind.)

Nicht lokale Verweise können nur über die -Funktionen abgerufen werden NewGlobalRef und NewWeakGlobalRef.

Wenn du eine Referenz länger aufbewahren möchtest, musst du ein „globales“ Referenz. Mit der Funktion NewGlobalRef werden die Werte lokalen Verweis als Argument und gibt ein globales Argument zurück. Die globale Referenz ist garantiert gültig, bis du sie aufrufst. DeleteGlobalRef

Dieses Muster wird häufig verwendet, wenn eine zurückgegebene jclass-Klasse im Cache gespeichert wird. aus FindClass, z.B.:

jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

Alle JNI-Methoden akzeptieren sowohl lokale als auch globale Referenzen als Argumente. Verweise auf dasselbe Objekt können unterschiedliche Werte haben. Die Rückgabewerte von aufeinanderfolgenden Aufrufen an NewGlobalRef für dasselbe Objekt kann unterschiedlich sein. Um zu sehen, ob sich zwei Verweise auf dasselbe Objekt beziehen, müssen Sie die Funktion IsSameObject verwenden. Nie vergleichen Referenzen mit == im nativen Code

Eine Konsequenz daraus ist, dass Sie darf nicht davon ausgehen, dass Objektverweise konstant oder eindeutig sind im nativen Code. Der Wert, der ein Objekt darstellt, kann abweichen von einem Aufruf einer Methode zum nächsten übertragen. Verschiedene Objekte können bei aufeinanderfolgenden Aufrufen denselben Wert haben. Nicht verwenden jobject-Werte als Schlüssel.

Programmierer müssen „nicht übermäßig viel“ lokale Referenzen. In der Praxis bedeutet das, Wenn Sie eine große Anzahl lokaler Referenzen erstellen, z. B. während Sie eine Reihe von -Objekten enthält, sollten Sie diese manuell mit DeleteLocalRef statt es JNI für Sie erledigen zu lassen. Die ist nur erforderlich, um Slots für 16 lokale Verweise. Wenn Sie also mehr brauchen, löschen Sie entweder den Text im Laufe der Zeit oder verwenden Sie EnsureLocalCapacity/PushLocalFrame, um mehr zu reservieren.

Beachten Sie, dass jfieldID- und jmethodID-Werte opak sind. -Typen und keine Objektverweise und sollten nicht an NewGlobalRef Die Rohdaten Cursor, die von Funktionen wie GetStringUTFChars zurückgegeben werden und GetByteArrayElements sind ebenfalls keine Objekte. (Sie werden möglicherweise zwischen Threads und sind bis zum entsprechenden Release-Aufruf gültig.)

Ein ungewöhnlicher Fall verdient eine gesonderte Erwähnung. Wenn Sie eine native mit AttachCurrentThread verbunden ist, wird der von Ihnen ausgeführte Code Lokale Verweise werden nie automatisch freigegeben, bis der Thread getrennt wird. Beliebige lokale Zielgruppe Referenzen, die Sie erstellen, müssen manuell gelöscht werden. Im Allgemeinen zum Erstellen lokaler Verweise in einer Schleife zu löschen.

Seien Sie bei der Verwendung globaler Referenzen vorsichtig. Globale Bezüge sind zwar unvermeidlich, aber schwierig zur Fehlerbehebung und kann schwierig zu diagnostizierende Arbeitsspeicher-/Fehlverhaltensweisen führen. Wenn alle anderen Bedingungen gleich sind, mit weniger globalen Verweisen ist wahrscheinlich besser.

UTF-8- und UTF-16-Strings

Die Programmiersprache Java verwendet UTF-16. Der Einfachheit halber bietet JNI Methoden, die mit UTF-8 geändert. Die Die modifizierte Codierung ist für C-Code nützlich, da \u0000 als 0xc0 0x80 und nicht als 0x00 codiert wird. Das Schöne daran ist, dass Sie sich darauf verlassen können, dass Sie nullterminierte C-Strings haben, Geeignet zur Verwendung mit Standard-libc-Stringfunktionen. Die Kehrseite ist, dass man nicht beliebige UTF-8-Daten an JNI senden und erwarten, dass diese korrekt funktionieren.

Zum Abrufen der UTF-16-Darstellung einer String verwenden Sie GetStringChars. Beachte, dass UTF-16-Strings nicht nullterminiert sind und „\u0000“ zulässig ist, Daher müssen Sie sich sowohl an die Zeichenfolgenlänge als auch an den jchar-Pointer halten.

Vergiss nicht, Release die Zeichenfolgen zu Get. Die Stringfunktionen geben jchar* oder jbyte* zurück. sind Zeiger im C-Stil auf primitive Daten statt auf lokale Bezüge. Sie sind garantiert gültig, bis Release aufgerufen wird. Das bedeutet, dass sie nicht wird freigegeben, wenn die native Methode zurückkehrt.

An NewStringUTF übergebene Daten müssen im geänderten UTF-8-Format vorliegen. A Häufiger Fehler ist das Lesen von Zeichendaten aus einer Datei oder einem Netzwerkstream und an NewStringUTF übergeben, ohne sie zu filtern. Sofern Sie nicht wissen, dass die Daten gültiger MUTF-8-Code (oder 7-Bit-ASCII, einer kompatiblen Teilmenge) sind, müssen Sie ungültige Zeichen entfernen oder in das richtige geänderte UTF-8-Format konvertieren. Andernfalls führt die UTF-16-Konvertierung wahrscheinlich zu unerwarteten Ergebnissen. CheckJNI – das für Emulatoren standardmäßig aktiviert ist – scannt Strings und bricht die VM ab, wenn sie ungültige Eingaben empfängt.

Vor Android 8 war es in der Regel schneller, als Android mit UTF-16-Strings zu arbeiten. erforderte keine Kopie in GetStringChars, während Für GetStringUTFChars waren eine Zuordnung und eine Konvertierung in UTF-8 erforderlich. Unter Android 8 wurde die String-Darstellung geändert, sodass jetzt 8 Bit pro Zeichen verwendet werden. für ASCII-Zeichenfolgen (um Arbeitsspeicher zu sparen) und begann, eine Umzug automatische Speicherbereinigung. Durch diese Funktionen sinkt die Zahl der Fälle, in denen ART kann einen Verweis auf die String-Daten bereitstellen, ohne eine Kopie zu erstellen, auch für GetStringCritical. Wenn die meisten vom Code verarbeiteten Strings die Zuordnung und Deallocation in den meisten Fällen zu vermeiden, mithilfe eines Stack-basierten Zwischenspeichers und GetStringRegion oder GetStringUTFRegion. Beispiel:

    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);

Primitive Arrays

JNI bietet Funktionen für den Zugriff auf die Inhalte von Array-Objekten. Auf Arrays von Objekten muss ein Eintrag nach dem anderen zugegriffen werden. Arrays von Primitive können direkt gelesen und geschrieben werden, als wären sie in C deklariert.

Um die Schnittstelle so effizient wie möglich zu gestalten, ohne die VM-Implementierung, die Get<PrimitiveType>ArrayElements kann die Laufzeit entweder einen Zeiger auf die tatsächlichen Elemente zurückgeben Arbeitsspeicher zuweisen und eine Kopie erstellen. In beiden Fällen hat der Rohzeiger zurückgegeben, ist garantiert bis zum entsprechenden Release-Aufruf gültig. ausgegeben wird (was impliziert, dass das Array-Objekt, falls die Daten nicht kopiert wurden, wird angepinnt und kann bei der Verdichtung des Heaps nicht verschoben werden). Sie müssen jedes Array Release Get. Wenn die Get -Aufruf fehlschlägt, müssen Sie darauf achten, dass Ihr Code nicht versucht, einen NULL-Wert Release zeig später hinzu.

Sie können feststellen, ob die Daten kopiert wurden, indem Sie eine Nicht-NULL-Zeiger für das Argument isCopy. Das kommt selten vor, nützlich sind.

Der Release-Aufruf nimmt ein mode-Argument an, das haben einen von drei Werten. Welche Aktionen von der Laufzeit ausgeführt werden, hängt davon ab, ob ein Zeiger auf die tatsächlichen Daten oder eine Kopie davon zurückgegeben wurde:

  • 0
    • Tatsächlich: Das Array-Objekt ist losgelöst.
    • Kopieren: Die Daten werden zurückkopiert. Der Zwischenspeicher mit der Kopie wird freigegeben.
  • JNI_COMMIT
    • Tatsächlich: nichts.
    • Kopieren: Die Daten werden zurückkopiert. Zwischenspeicher mit der Kopie wird nicht freigegeben.
  • JNI_ABORT
    • Tatsächlich: Das Array-Objekt ist losgelöst. Früher Schreibvorgänge werden nicht abgebrochen.
    • Copy: Der Zwischenspeicher mit der Kopie wird freigegeben. gehen alle Änderungen verloren.

Ein Grund für die Überprüfung des Flags isCopy besteht darin, zu wissen, Du musst Release mit JNI_COMMIT anrufen nachdem Sie Änderungen an einem Array vorgenommen haben – wenn Sie zwischen und Code ausführen, der den Inhalt des Arrays verwendet, können den No-Op-Commit überspringen. Ein weiterer möglicher Grund für die Überprüfung der Kennzeichnung ist effiziente Handhabung von JNI_ABORT. Vielleicht möchten Sie zum Beispiel um ein Array zu erhalten, es an Ort und Stelle zu ändern, Teile an andere Funktionen zu übergeben Verwerfen Sie die Änderungen. Wenn Sie wissen, dass JNI eine neue Kopie ist es nicht erforderlich, eine weitere kopieren. Wenn JNI müssen Sie eine eigene Kopie erstellen.

Es ist ein häufiger Fehler (wird im Beispielcode wiederholt), dass man den Release-Aufruf überspringen kann, wenn *isCopy ist falsch. Das ist nicht der Fall. Wenn kein Kopierpuffer vorhanden war wird der ursprüngliche Arbeitsspeicher angepinnt und kann nicht durch für die automatische Speicherbereinigung.

Beachten Sie außerdem, dass das Flag JNI_COMMIT das Array nicht freigibt. und Sie müssen Release noch einmal mit einem anderen Flag aufrufen werden.

Regionsaufrufe

Es gibt eine Alternative zu Anrufen wie Get<Type>ArrayElements und GetStringChars, die sehr hilfreich sein können, wenn Sie Daten einfach hinein- oder herauszukopieren. Hier einige Tipps:

    jbyte* data = env->GetByteArrayElements(array, NULL);
    if (data != NULL) {
        memcpy(buffer, data, len);
        env->ReleaseByteArrayElements(array, data, JNI_ABORT);
    }

Dadurch wird das Array abgerufen und das erste Byte (len) wird kopiert. Elemente und gibt dann das Array frei. Je nach implementiert, wird das Array durch den Get-Aufruf entweder angepinnt oder kopiert. Inhalte. Der Code kopiert die Daten (etwa ein zweites Mal) und ruft dann Release auf. in diesem Fall Mit JNI_ABORT wird verhindert, dass eine dritte Kopie erstellt wird.

Das Gleiche kann auch einfacher erreicht werden:

    env->GetByteArrayRegion(array, 0, len, buffer);

Dies hat mehrere Vorteile:

  • Erfordert einen JNI-Aufruf statt zwei, wodurch der Aufwand reduziert wird.
  • Es ist kein Anpinnen oder zusätzliche Kopieren von Daten erforderlich.
  • Reduziert das Risiko von Programmierfehlern – kein Risiko, dass sie vergessen werden um Release aufzurufen, wenn ein Fehler auftritt.

Ebenso können Sie den Aufruf Set<Type>ArrayRegion verwenden, um Daten in ein Array zu kopieren, und GetStringRegion oder GetStringUTFRegion, um Zeichen aus einem String.

Ausnahmen

Die meisten JNI-Funktionen dürfen nicht aufgerufen werden, solange eine Ausnahme ansteht. Es wird erwartet, dass Ihr Code die Ausnahme bemerkt (über den Rückgabewert der Funktion, ExceptionCheck oder ExceptionOccurred) und zurück, oder die Ausnahme löschen und verarbeiten.

Die einzigen JNI-Funktionen, die Sie während einer Ausnahme aufrufen dürfen, ausstehend:

  • DeleteGlobalRef
  • DeleteLocalRef
  • DeleteWeakGlobalRef
  • ExceptionCheck
  • ExceptionClear
  • ExceptionDescribe
  • ExceptionOccurred
  • MonitorExit
  • PopLocalFrame
  • PushLocalFrame
  • Release<PrimitiveType>ArrayElements
  • ReleasePrimitiveArrayCritical
  • ReleaseStringChars
  • ReleaseStringCritical
  • ReleaseStringUTFChars

Viele JNI-Aufrufe können eine Ausnahme auslösen, bieten aber oft eine einfachere Methode. auf Fehler zu prüfen. Wenn z. B. NewString den Wert Nicht-NULL-Wert ist, müssen Sie nicht nach einer Ausnahme suchen. Wenn jedoch Sie eine Methode aufrufen (mit einer Funktion wie CallObjectMethod), müssen Sie immer nach einer Ausnahme suchen, da der Rückgabewert ist gültig, wenn eine Ausnahme ausgelöst wird.

Beachten Sie, dass Ausnahmen, die von verwaltetem Code ausgegeben werden, den nativen Stack nicht rückgängig machen. Frames. (Und C++-Ausnahmen, von denen unter Android in der Regel abgeraten wird, dürfen nicht über die JNI-Übergangsgrenze vom C++ Code zum verwalteten Code geworfen werden.) Die Anweisungen für JNI Throw und ThrowNew einen Ausnahmezeiger im aktuellen Thread setzen. Nach Rückkehr zum verwalteten Modus aus nativem Code entfernt wird, wird die Ausnahme vermerkt und entsprechend gehandhabt.

Nativer Code kann „einfangen“, Ausnahmeregelung durch Aufrufen von ExceptionCheck oder ExceptionOccurred und löschen Sie sie mit ExceptionClear Wie immer wenn Ausnahmen verworfen werden, ohne sie zu verarbeiten, kann dies zu Problemen führen.

Es gibt keine integrierten Funktionen zum Bearbeiten des Throwable-Objekts sich selbst. Wenn Sie also (z. B.) die Ausnahmezeichenfolge abrufen möchten, müssen Sie die Throwable-Klasse finden, die Methoden-ID für getMessage "()Ljava/lang/String;", ruft es auf und wenn das Ergebnis nicht NULL ist, verwenden Sie GetStringUTFChars, um etwas zu erhalten, an printf(3) oder eine gleichwertige Stelle senden.

Erweiterte Prüfung

JNI führt nur sehr wenige Fehlerprüfungen durch. Fehler führen in der Regel zu einem Absturz. Android bietet auch einen Modus namens CheckJNI, in dem die Funktionstabellenzeiger JavaVM und JNIEnv auf Funktionstabellen umgestellt werden, die eine erweiterte Reihe von Prüfungen durchführen, bevor die Standardimplementierung aufgerufen wird.

Zu den zusätzlichen Prüfungen gehören:

  • Arrays: Versuch, ein Array von geringer Größe zuzuordnen.
  • Ungültige Zeiger: Übergabe eines fehlerhaften jarray/jclass/jobject/jstring an einen JNI-Aufruf oder Übergabe eines NULL-Zeigers an einen JNI-Aufruf mit einem Argument, das keine Nullwerte zulässt.
  • Klassennamen: Beliebige Werte außer dem Stil „java/lang/String“ des Klassennamens an einen JNI-Aufruf übergeben.
  • Kritische Aufrufe: Ein JNI-Aufruf zwischen einem „kritischen“ Get und dem entsprechenden Release wird ausgeführt.
  • Direct ByteBuffers: Übergeben ungültiger Argumente an NewDirectByteBuffer
  • Ausnahmen: Ausführen eines JNI-Aufrufs, während eine Ausnahme aussteht.
  • JNIEnv*s: JNIEnv* aus dem falschen Thread verwenden.
  • jfieldIDs: Verwenden einer NULL-jfieldID oder Verwenden einer jfieldID zum Festlegen eines Felds auf einen Wert des falschen Typs (z. B. der Versuch, einem String-Feld einen StringBuilder zuzuweisen) oder die Verwendung von jfieldID für ein statisches Feld, um ein Instanzfeld festzulegen oder umgekehrt, oder Verwenden einer jfieldID aus einer Klasse mit Instanzen einer anderen Klasse.
  • jmethodIDs: Verwendung der falschen Art von jmethodID bei einem Call*Method-JNI-Aufruf: falscher Rückgabetyp, nicht übereinstimmender statischer/nicht statischer Parameter, falscher Typ für „this“ (bei nicht statischen Aufrufen) oder falsche Klasse (bei statischen Aufrufen).
  • Referenzen: Verwendung von DeleteGlobalRef/DeleteLocalRef für die falsche Art von Referenz.
  • Release-Modi: Übergeben eines fehlerhaften Release-Modus an einen Release-Aufruf (etwas anderes als 0, JNI_ABORT oder JNI_COMMIT).
  • Typsicherheit: Rückgabe eines inkompatiblen Typs von Ihrer nativen Methode (z. B. Rückgabe eines StringBuilders aus einer deklarierten Methode, um einen String zurückzugeben).
  • UTF-8: Eine ungültige Geänderte UTF-8-Byte-Sequenz wird an einen JNI-Aufruf übergeben.

Die Zugänglichkeit von Methoden und Feldern wird weiterhin nicht geprüft: Zugriffsbeschränkungen gelten nicht für nativen Code.

Es gibt mehrere Möglichkeiten, CheckJNI zu aktivieren.

Wenn Sie den Emulator verwenden, ist CheckJNI standardmäßig aktiviert.

Wenn Sie ein gerootetes Gerät haben, können Sie die folgende Befehlsfolge verwenden, um die Laufzeit mit aktiviertem CheckJNI neu zu starten:

adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start

In beiden Fällen wird beim Start der Laufzeit in der Logcat-Ausgabe in etwa Folgendes angezeigt:

D AndroidRuntime: CheckJNI is ON

Wenn Sie ein normales Gerät haben, können Sie den folgenden Befehl verwenden:

adb shell setprop debug.checkjni 1

Dies hat keine Auswirkungen auf bereits ausgeführte Apps, aber für alle Apps, die ab diesem Zeitpunkt gestartet werden, ist CheckJNI aktiviert. (Ändern Sie die Eigenschaft in einen anderen Wert oder starten Sie einfach einen Neustart, um CheckJNI wieder zu deaktivieren.) In diesem Fall sehen Sie beim nächsten Start einer Anwendung in Ihrer Logcat-Ausgabe etwa Folgendes:

D Late-enabling CheckJNI

Sie können auch das Attribut android:debuggable im Manifest Ihrer App auf und aktiviere CheckJNI nur für deine App. Die Android-Build-Tools erledigen dies automatisch für bestimmte Build-Typen verwenden.

Native Bibliotheken

Sie können nativen Code aus gemeinsam genutzten Bibliotheken mit dem Standardcode System.loadLibrary

In der Praxis traten bei älteren Android-Versionen Fehler im PackageManager auf, die die Installation und native Bibliotheken unzuverlässig zu aktualisieren. ReLinker bietet Problemumgehungen für dieses und andere Ladeprobleme beim Laden der nativen Bibliothek.

System.loadLibrary (oder ReLinker.loadLibrary) aus einer statischen Klasse aufrufen Initialisieren. Das Argument ist die Bibliotheksname, Zum Laden von libfubar.so würden Sie also "fubar" übergeben.

Wenn Sie nur eine Klasse mit nativen Methoden haben, ist es sinnvoll, für den Aufruf der System.loadLibrary in einem statischen Initialisierer für diese Klasse sein. Andernfalls können Sie möchten den Anruf über Application starten, damit Sie wissen, dass die Bibliothek immer geladen ist, und immer früh geladen.

Es gibt zwei Möglichkeiten, wie die Laufzeit Ihre nativen Methoden finden kann. Sie können entweder explizit registrieren Sie sie bei RegisterNatives oder lassen Sie die Laufzeit dynamisch nachschlagen. mit dlsym. Die Vorteile von RegisterNatives sind, dass Sie sofort loslegen können. Überprüfen Sie, ob die Symbole vorhanden sind. Außerdem können Sie kleinere und schnellere gemeinsam genutzte Bibliotheken nutzen, indem Sie Export von etwas außer JNI_OnLoad. Der Vorteil, wenn die Laufzeit Ihre ist, dass weniger Code zum Schreiben benötigt wird.

Gehe so vor, wenn du RegisterNatives verwenden möchtest:

  • Stellen Sie eine JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)-Funktion bereit.
  • Registrieren Sie in JNI_OnLoad alle nativen Methoden mit RegisterNatives.
  • Verwende -fvisibility=hidden, damit nur deine JNI_OnLoad wird aus Ihrer Bibliothek exportiert. Dadurch wird schnellerer und kleinerer Code generiert und potenzielle Kollisionen mit anderen Bibliotheken, die in Ihre App geladen wurden (erzeugt aber weniger nützliche Stacktraces) wenn Ihre App in nativem Code abstürzt).

Der statische Initialisierer sollte so aussehen:

Kotlin

companion object {
    init {
        System.loadLibrary("fubar")
    }
}

Java

static {
    System.loadLibrary("fubar");
}

Die Funktion JNI_OnLoad sollte in etwa so aussehen, geschrieben in 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;
}

Um stattdessen „Discovery“ zu verwenden nativer Methoden verwenden, müssen Sie ihnen eine bestimmte Bezeichnung geben (siehe JNI-Spezifikation . Wenn also eine Methodensignatur falsch ist, wissen Sie erst darüber Bescheid, wenn die Methode zum ersten Mal aufgerufen wird.

Alle FindClass-Aufrufe von JNI_OnLoad führen zu Klassen in der Kontext des Klassenladeprogramms, das zum Laden der gemeinsam genutzten Bibliothek verwendet wurde. Bei Anruf von einem anderen Anbieter Kontexte verwendet, verwendet FindClass das Klassenladeprogramm, das mit der Methode oben im Java-Stack oder wenn es keinen gibt (der Aufruf stammt aus einem nativen Thread, der gerade angehängt wurde) wird das „System“- Klassenladeprogramm. Das Systemklassenladeprogramm kennt die Kurse. Sie können daher Ihre eigenen Kurse mit FindClass nicht suchen. Kontext. Dadurch ist JNI_OnLoad ein praktischer Ort, um Klassen nachzuschlagen und im Cache zu speichern: Sie haben eine gültige globale Referenz für jclass können Sie sie in jedem angehängten Thread verwenden.

Schnellere native Anrufe mit @FastNative und @CriticalNative

Native Methoden können mit @FastNative oder @CriticalNative (aber nicht beides), um den Übergang zwischen verwaltetem und nativem Code zu beschleunigen. Diese Anmerkungen bringen gewisse Verhaltensänderungen mit sich, die vor der Nutzung sorgfältig bedacht werden müssen. Während wir unten kurz diese Änderungen erwähnen. Einzelheiten finden Sie in der Dokumentation.

Die Annotation @CriticalNative kann nur auf native Methoden angewendet werden, die keine verwaltete Objekte verwenden (in Parametern oder Rückgabewerten oder als implizites this) und verändert den JNI-Übergang ABI. In der nativen Implementierung muss Folgendes ausgeschlossen werden: JNIEnv- und jclass-Parameter aus ihrer Funktionssignatur.

Beim Ausführen der Methode @FastNative oder @CriticalNative Sammlung kann den Thread nicht für wichtige Aufgaben sperren und wird möglicherweise blockiert. Nicht verwenden Annotationen für Methoden mit langer Ausführungszeit, einschließlich in der Regel schnellen, aber im Allgemeinen unbegrenzten Methoden. Insbesondere sollte der Code keine signifikanten E/A-Vorgänge ausführen oder native Sperren erhalten, die lange gehalten werden können.

Diese Anmerkungen wurden für das System implementiert, seit Android 8 und wurden von CTS getestet, API unter Android 14. Wahrscheinlich funktionieren diese Optimierungen auch auf Geräten mit Android 8 bis 13, ohne die starken CTS-Garantien), aber die dynamische Suche nach nativen Methoden wird nur auf Android 12 oder höher, die ausdrückliche Registrierung bei JNI RegisterNatives ist zwingend erforderlich für Android-Versionen 8 bis 11. Diese Anmerkungen werden unter Android 7 ignoriert, die ABI-Abweichung. für @CriticalNative würde zu einem falschen Argument-Marshalling und wahrscheinlichen Abstürzen führen.

Für leistungskritische Methoden, die diese Vermerke benötigen, wird dringend empfohlen, die Methode(n) explizit bei JNI RegisterNatives registrieren, anstatt sich auf die Namensbasierte „Entdeckung“ nativer Methoden. Für eine optimale Startleistung der App wird empfohlen, um Aufrufer der Methoden @FastNative oder @CriticalNative in die Methode Baseline-Profil verwenden. Seit Android 12 Der Aufruf einer nativen @CriticalNative-Methode über eine kompilierte verwaltete Methode ist fast genauso billig als Nicht-Inline-Aufruf in C/C++, solange alle Argumente in Register passen (zum Beispiel bis zu 8 Integral- und bis zu 8 Gleitkommaargumente auf Arm64).

Manchmal ist es besser, eine native Methode in zwei Teile aufzuteilen. Dies ist eine sehr schnelle Methode, und eine weitere, die langsame Fälle abwickelt. Beispiel:

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);

Hinweise zur 64-Bit-Version

Zur Unterstützung von Architekturen mit 64-Bit-Zeigern sollten Sie das Feld long anstelle eines int beim Speichern eines Zeigers auf eine native Struktur in einem Java-Feld.

Nicht unterstützte Funktionen/Abwärtskompatibilität

Bis auf die folgende Ausnahme werden alle Funktionen von JNI 1.6 unterstützt:

  • DefineClass ist nicht implementiert. Android verwendet nicht Java-Bytecodes oder Klassendateien, sodass binäre Klassendaten übergeben werden. nicht funktioniert.

Für die Abwärtskompatibilität mit älteren Android-Releases musst du möglicherweise beachten Sie Folgendes:

  • Dynamische Suche nach nativen Funktionen

    Bis Android 2.0 (Eclair) wird das "$"-Zeichen Zeichen war nicht richtig konvertiert in "_00024" bei der Suche nach Methodennamen. Wird ausgeführt eine explizite Registrierung oder das Verschieben des aus inneren Klassen herauszufiltern.

  • Threads werden getrennt

    Vor Android 2.0 (Eclair) war es nicht möglich, pthread_key_create destruktor, um zu vermeiden, dass der Thread beenden" überprüfen. Die Laufzeit verwendet außerdem eine pthread-Destrukturorfunktion, Wer zuerst herausfindet, soll herausfinden, welcher der beiden zuerst aufgerufen wird.

  • Schwache globale Referenzen

    Bis Android 2.2 (Froyo) wurden schwache globale Referenzen nicht implementiert. Versuche, sie zu verwenden, werden von älteren Versionen strikt abgelehnt. Sie können Versionskonstanten der Android-Plattform, um die Unterstützung zu testen.

    Bis Android 4.0 (Ice Cream Sandwich) konnten schwache globale Referenzen an NewLocalRef, NewGlobalRef und DeleteWeakGlobalRef (In der Spezifikation wird ausdrücklich empfohlen, harte Verweise auf schwache globale Daten zu erstellen, nichts mit ihnen zu tun, also sollte dies überhaupt nicht einschränken.)

    Ab Android 4.0 (Ice Cream Sandwich) können schwache globale Verweise durch wie alle anderen JNI-Referenzen verwendet werden.

  • Lokale Verweise

    Bis Android 4.0 (Ice Cream Sandwich) waren lokale Verweise direkte Verweise. Ice Cream Sandwich hat die Indirektion hinzugefügt. um bessere automatische Speicherbereinigungen zu unterstützen. der JNI-Fehler sind in älteren Releases nicht erkennbar. Weitere Informationen finden Sie unter <ph type="x-smartling-placeholder"></ph> Änderungen der lokalen JNI-Referenz in ICS.

    In früheren Android-Versionen vor Android 8.0 Die Anzahl der lokalen Verweise ist auf ein versionsspezifisches Limit begrenzt. Ab Android 8.0 Android unterstützt unbegrenzte lokale Verweise.

  • Referenztyp mit GetObjectRefType ermitteln

    Bis Android 4.0 (Ice Cream Sandwich), da die Verwendung von direkte Verweise (siehe oben) enthält, war es nicht möglich, GetObjectRefType richtig. Stattdessen haben wir eine Heuristik die Tabelle der schwachen globalen Konzerne, die Argumente, die lokalen und der globalen Tabelle in dieser Reihenfolge. Das erste Mal, als dein wird angegeben, dass ihr Verweis vom Typ war, die gerade untersucht wurde. Das bedeutete zum Beispiel, Sie haben GetObjectRefType in einem globalen jclass aufgerufen, muss mit der jclass identisch sein, die als implizites Argument an Ihre statische erhalten Sie JNILocalRefType statt JNIGlobalRefType

  • @FastNative und @CriticalNative

    Bis Android 7 wurden diese Optimierungsanmerkungen ignoriert. Das ABI Eine Abweichung für @CriticalNative würde zu einem falschen Argument führen. und es kommt wahrscheinlich zu Abstürzen.

    Dynamische Suche nach nativen Funktionen für @FastNative und @CriticalNative-Methoden wurden in Android 8-10 nicht implementiert und enthält bekannte Fehler in Android 11. Wenn Sie diese Optimierungen ohne explizite Registrierung bei JNI RegisterNatives wahrscheinlich zu Abstürzen unter Android 8 bis 11 führen.

  • FindClass wirft ClassNotFoundException

    Aus Gründen der Abwärtskompatibilität wirft Android ClassNotFoundException statt NoClassDefFoundError, wenn ein Kurs nicht über FindClass Dieses Verhalten entspricht der Java Reflexion API. Class.forName(name)

FAQ: Warum erhalte ich UnsatisfiedLinkError?

Bei der Arbeit an nativem Code treten häufig folgende Fehler auf:

java.lang.UnsatisfiedLinkError: Library foo not found

Manchmal bedeutet das, dass die Bibliothek nicht gefunden wurde. In In anderen Fällen ist die Bibliothek vorhanden, konnte aber nicht von dlopen(3) geöffnet werden. Die Details des Fehlers finden Sie in der Detailnachricht zur Ausnahme.

Häufige Gründe für die Meldung „Bibliothek nicht gefunden“ Ausnahmen:

  • Die Bibliothek existiert nicht oder die App kann nicht darauf zugreifen. Verwenden Sie adb shell ls -l <path>, um die Anwesenheit zu prüfen und Berechtigungen.
  • Die Bibliothek wurde nicht mit dem NDK entwickelt. Dies kann zu Abhängigkeiten von Funktionen oder Bibliotheken, die nicht auf dem Gerät vorhanden sind.

Eine andere Klasse von UnsatisfiedLinkError-Fehlern sieht so aus:

java.lang.UnsatisfiedLinkError: myfunc
        at Foo.myfunc(Native Method)
        at Foo.main(Foo.java:10)

In logcat sehen Sie Folgendes:

W/dalvikvm(  880): No implementation found for native LFoo;.myfunc ()V

Das bedeutet, dass die Laufzeit versucht hat, eine Abgleichmethode zu finden, nicht erfolgreich. Hier einige häufige Gründe dafür:

  • Die Bibliothek wird nicht geladen. Prüfen Sie die Logcat-Ausgabe auf zum Laden der Mediathek.
  • Die Methode kann aufgrund eines nicht übereinstimmenden Namens oder der Signatur nicht gefunden werden. Dieses wird häufig durch folgende Ursachen verursacht: <ph type="x-smartling-placeholder">
      </ph>
    • Bei verzögerter Methodensuche werden keine C++-Funktionen deklariert mit extern "C" und angemessen Sichtbarkeit (JNIEXPORT) Hinweis: Vor Ice Cream Sandwich enthält, war das JNIEXPORT-Makro inkorrekt, sodass die Verwendung eines neuen GCC mit Ein altes jni.h funktioniert nicht. Du kannst arm-eabi-nm verwenden um die Symbole so anzuzeigen, wie sie in der Bibliothek erscheinen. wenn sie verzerrt (etwa _Z15Java_Foo_myfuncP7_JNIEnvP7_jclass) statt Java_Foo_myfunc) oder wenn der Symboltyp ein kleingeschriebenes „t“ statt eines großen 'T' müssen Sie die Deklaration anpassen.
    • Für eine explizite Registrierung können kleine Fehler bei der Eingabe des Methodensignatur. Achten Sie darauf, dass die Daten, die Sie an die Registrierungsaufruf mit der Signatur in der Protokolldatei übereinstimmt. Denken Sie daran, dass „B“ ist byte und „Z“ ist boolean. Komponenten von Klassennamen in Signaturen beginnen mit „L“ und enden mit „;“, Verwenden Sie '/' zum Trennen von Paket-/Klassennamen und verwenden Sie '$' zum Trennen einer Namen der inneren Klassen (z. B. Ljava/util/Map$Entry;).

Die Verwendung von javah zur automatischen Generierung von JNI-Headern kann hilfreich sein Probleme zu vermeiden.

FAQ: Warum hat FindClass meinen Kurs nicht gefunden?

(Die meisten dieser Tipps gelten auch für Fehler beim Finden von Methoden. mit GetMethodID oder GetStaticMethodID oder Feldern mit GetFieldID oder GetStaticFieldID.)

Achten Sie darauf, dass der String für den Klassennamen das richtige Format hat. JNI-Klasse beginnen mit dem Paketnamen und werden durch Schrägstriche getrennt. wie java/lang/String. Wenn Sie nach einer Array-Klasse suchen, beginnen Sie mit der entsprechenden Anzahl eckiger Klammern muss die Klasse auch mit „L“ umschließen. und ';', also ein eindimensionales Array von String wäre [Ljava/lang/String;. Wenn du nach einer inneren Klasse suchst, verwende "$". statt ".". Im Allgemeinen javap in der .class-Datei ist eine gute Möglichkeit, internen Namen Ihres Kurses an.

Wenn Sie die Codekomprimierung aktivieren, konfigurieren, welcher Code beibehalten werden soll. Wird konfiguriert ist wichtig, weil der Code-Crumker sonst Klassen, Methoden, oder Felder, die nur von JNI verwendet werden.

Wenn der Klassenname stimmt, wird möglicherweise ein Klassenladeprogramm angezeigt. Problem. FindClass möchte die Kurssuche starten in: Klassenladeprogramm, das mit Ihrem Code verknüpft ist. Es prüft den Aufrufstack, Das sieht ungefähr so aus:

    Foo.myfunc(Native Method)
    Foo.main(Foo.java:10)

Die oberste Methode ist Foo.myfunc. FindClass findet das ClassLoader-Objekt, das mit Foo verknüpft ist. verwendet.

Das tut in der Regel, was Sie möchten. Sie können in Schwierigkeiten geraten, wenn Sie Thread selbst erstellen (z. B. durch Aufrufen von pthread_create) und es dann mit AttachCurrentThread anhängen). Jetzt Es gibt keine Stack-Frames aus Ihrer Anwendung. Wenn Sie FindClass aus diesem Thread aufrufen, JavaVM startet im „System“ Klassenladeprogramm anstelle des zugehörigen mit Ihrer Anwendung. Daher schlagen Versuche, App-spezifische Klassen zu finden, fehl.

Es gibt mehrere Möglichkeiten, dieses Problem zu umgehen:

  • Führen Sie eine FindClass-Suche einmal durch, in JNI_OnLoad und speichern Sie die Klassenreferenzen zur späteren Verwendung im Cache. verwenden. Alle FindClass-Aufrufe, die im Rahmen der Ausführung erfolgen JNI_OnLoad verwendet das Klassenladeprogramm, das verknüpft ist mit die Funktion System.loadLibrary (dies ist ein Sonderregel, die dazu dient, die Initialisierung der Bibliothek zu vereinfachen. Wenn die Bibliothek über deinen App-Code geladen wird, FindClass verwendet das richtige Klassenladeprogramm.
  • Übergeben Sie eine Instanz der Klasse an die Funktionen, die indem Sie Ihre native Methode so deklarieren, dass ein Klassenargument verwendet wird und und dann Foo.class übergeben.
  • Speichern Sie einen Verweis auf das ClassLoader-Objekt im Cache. und direkt loadClass-Anrufe senden. Dies erfordert etwas Anstrengung.

FAQ: Wie kann ich Rohdaten mit nativem Code teilen?

Möglicherweise benötigen Sie Zugriff auf eine große Rohdaten aus verwaltetem und nativem Code speichern. Gängige Beispiele die Bearbeitung von Bitmaps oder Tonbeispielen beinhalten. Es gibt zwei grundlegende Ansätze an.

Sie können die Daten in einem byte[] speichern. Dies ermöglicht sehr schnelle Zugriff über verwalteten Code. Auf der nativen Seite hingegen nicht garantiert, dass Sie auf die Daten zugreifen können, ohne sie kopieren zu müssen. In einige Implementierungen, GetByteArrayElements und GetPrimitiveArrayCritical gibt tatsächliche Zeiger auf die Rohdaten werden im verwalteten Heap gespeichert, in anderen wird aber ein Zwischenspeicher zugewiesen. auf dem nativen Heap und kopieren die Daten hinüber.

Die Alternative besteht darin, die Daten in einem direkten Byte-Zwischenspeicher zu speichern. Diese können mit java.nio.ByteBuffer.allocateDirect erstellt werden oder die JNI-Funktion NewDirectByteBuffer. Anders als normales Bytepuffer, wird der Speicher nicht auf dem verwalteten Heap zugewiesen und kann kann immer direkt aus dem nativen Code heraus aufgerufen werden (die Adresse mit GetDirectBufferAddress). Je nachdem, wie direkt Es ist ein Byte-Pufferzugriff implementiert, wodurch der Zugriff auf die Daten aus dem verwalteten Code erfolgt. sehr langsam sein.

Die Wahl hängt von zwei Faktoren ab:

  1. Erfolgen die meisten Datenzugriffe über Code, der in Java geschrieben ist? oder in C/C++?
  2. In welcher Form werden die Daten schließlich an eine System-API übergeben? muss es drin sein? Wenn die Daten z. B. irgendwann an einen -Funktion, die ein byte[] verwendet und die Verarbeitung in einem direkten ByteBuffer könnte unklug sein.)

Wenn es keinen eindeutigen Gewinner gibt, verwenden Sie einen direkten Byte-Zwischenspeicher. Unterstützung für sie ist direkt in JNI integriert und die Leistung sollte sich in zukünftigen Releases verbessern.