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
oderJNI_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 mitRegisterNatives
. - Verwende
-fvisibility=hidden
, damit nur deineJNI_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
undDeleteWeakGlobalRef
(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
ermittelnBis 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 habenGetObjectRefType
in einem globalen jclass aufgerufen, muss mit der jclass identisch sein, die als implizites Argument an Ihre statische erhalten SieJNILocalRefType
stattJNIGlobalRefType
@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 JNIRegisterNatives
wahrscheinlich zu Abstürzen unter Android 8 bis 11 führen.FindClass
wirftClassNotFoundException
Aus Gründen der Abwärtskompatibilität wirft Android
ClassNotFoundException
stattNoClassDefFoundError
, wenn ein Kurs nicht überFindClass
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 altesjni.h
funktioniert nicht. Du kannstarm-eabi-nm
verwenden um die Symbole so anzuzeigen, wie sie in der Bibliothek erscheinen. wenn sie verzerrt (etwa_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
) stattJava_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“ istboolean
. 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;
).
- Bei verzögerter Methodensuche werden keine C++-Funktionen deklariert
mit
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, inJNI_OnLoad
und speichern Sie die Klassenreferenzen zur späteren Verwendung im Cache. verwenden. AlleFindClass
-Aufrufe, die im Rahmen der Ausführung erfolgenJNI_OnLoad
verwendet das Klassenladeprogramm, das verknüpft ist mit die FunktionSystem.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 direktloadClass
-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:
- Erfolgen die meisten Datenzugriffe über Code, der in Java geschrieben ist? oder in C/C++?
- 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.