JNI—java native interfacehtml
官方文檔java
JNI is the Java Native Interface. It defines a way for managed code (written in the Java programming language) to interact with native code (written in C/C++). It's vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.linux
JNI 即java 本地接口,它定義了一種方法(用java語言寫)管理代碼和本地代碼(用C語言或者C++語言)交互。它是中立,它支持動態地從分享庫中加載代碼,雖然操做會麻煩些,但有時候它是必須的,有效的。android
If you're not already familiar with it, read through the Java Native Interface Specification to get a sense for how JNI works and what features are available. Some aspects of the interface aren't immediately obvious on first reading, so you may find the next few sections handy.程序員
若是你以前並不熟悉它,經過閱讀這java 本地接口規範去了解JNI是如何工做的和一些有用的功能特性。第一次閱讀接口的某些方面不能馬上明白,然而你會在後面幾個部分中發現它。shell
JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentially pointers to pointers to function tables. (In the C++ version, they're classes with a pointer to a function table and a member function for each JNI function that indirects through the table.) The JavaVM provides the "invocation interface" functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but Android only allows one.數組
JNI定義了兩個重要的數據結構,javavm和JNIEnv。這二者本質上就是指向函數的指針構成的表。(在C++版本,該段實在翻譯不出來),javaVM(java虛擬機) 提供了調用接口的功能函數。這些接口功能函數容許你去建立和銷燬一個java虛擬機,理論上每一個進程你能夠有多個虛擬機,可是android只容許你有一個。緩存
The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument.數據結構
JNI(JNINativeInterface) oracle
提供了不少java 本地接口函數,你的全部的本地方法都接收一個JNIEnv做爲第一個參數
The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. If a piece of code has no other way to get its JNIEnv, you should share the JavaVM, and use GetEnv
to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread
below.)
JNIEnv是使用本地線程存儲的,出於這個緣由,你不能在線程間共享JNIEnv。若是一段代碼沒有其餘的方法去獲得JNIEnv,你應該共享java虛擬機而後使用GetEnv查找這線程中的JNIEnv
The C declarations of JNIEnv and JavaVM are different from the C++ declarations. The "jni.h"
include file provides different typedefs depending on whether it's included into C or C++. For this reason it's a bad idea to include JNIEnv arguments in header files included by both languages. (Put another way: if your header file requires #ifdef __cplusplus
, you may have to do some extra work if anything in that header refers to JNIEnv.)
JNIEnv 和JAVA虛擬機在C中的聲明與在C++中聲明是不一樣的。這個jni.h所包含的文件提供了不一樣的類型取決因而否包含C或者C++。出於這個緣由,在兩種語言的頭文件中包含JNIEnv參數是很是不可取的。(換句話說:若是你的具備JNIEnv的頭文件引用須要#ifdef __cplusplus標籤,你可能不得很少作一些 額外的工做
All threads are Linux threads, scheduled by the kernel. They're usually started from managed code (usingThread.start
), but they can also be created elsewhere and then attached to the JavaVM. For example, a thread started with pthread_create
can be attached with the JNI AttachCurrentThread
orAttachCurrentThreadAsDaemon
functions. Until a thread is attached, it has no JNIEnv, and cannot make JNI calls.
全部的線程都是linux線程,這些線程被kernet(內核)調度。線程一般從託管代碼開啓(使用 thread.start),可是他們也能夠在其餘地方被建立而後與java虛擬機相鏈接。例如,一個從pthread_create開始的線程能夠和JNI函數鏈接在一塊兒(經過AttachCurrentThread
orAttachCurrentThreadAsDaemon
)。直到一個可鏈接的線程由於沒有JNIEnv不能被JNI調用。
Attaching a natively-created thread causes a java.lang.Thread
object to be constructed and added to the "main" ThreadGroup
, making it visible to the debugger. Calling AttachCurrentThread
on an already-attached thread is a no-op.
一個了鏈接的本地線程被建立引發一個java.lang.Thread
對象唄構造出阿里並增長到主線程組中,打開可視化的調試,在一個早已鏈接的線程中調用AttachCurrentThread會等待。
Android does not suspend threads executing native code. If garbage collection is in progress, or the debugger has issued a suspend request, Android will pause the thread the next time it makes a JNI call.
Android 不支持線程執行不低代碼,若是垃圾回收器正在處理垃圾,或者調試已經發出了一個調試請求,Android將會暫停下次調用JNI的線程
Threads attached through JNI must call DetachCurrentThread
before they exit. If coding this directly is awkward, in Android 2.0 (Eclair) and higher you can use pthread_key_create
to define a destructor function that will be called before the thread exits, and call DetachCurrentThread
from there. (Use that key withpthread_setspecific
to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
經過JNI鏈接的線程在他們退出以前必須滴啊用DetachCurrentThread方法。若是直接編譯困在,再ANdroid2.0或者更高版本的系統上你可使用pthread_key_create
去定義一個在線程退出時被調用的析構函數。使用用pthread_setspecific鍵
在本地線程中去保存JNIEnv;它將能被你的析構函數做爲參數調用
If you want to access an object's field from native code, you would do the following:
若是你想從本地代碼中去接受一個對象字段。須要作以下操做
FindClass
使用findClass獲得類對象的引用
GetFieldID
GetIntField
GetIntField
獲得字段中某些內容信息Similarly, to call a method, you'd first get a class object reference and then a method ID. The IDs are often just pointers to internal runtime data structures. Looking them up may require several string comparisons, but once you have them the actual call to get the field or invoke the method is very quick.
相似的,你要去調用一個方法,首先要得到一個類的對象的引用和這個方法的ID。ID經常只是內部運行的結構體的指針。查看他們可能須要比較幾個字符串。可是一旦你獲得了他們自己再去獲得字段或者調用方法是很是容易的。
If performance is important, it's useful to look the values up once and cache the results in your native code. Because there is a limit of one JavaVM per process, it's reasonable to store this data in a static local structure.
若是要求高性能,在你的本地代碼中經常查看一次這個值並緩存起來。由於這是java虛擬機的一個限制,將這些數據用靜態局部結構體存儲起來是頗有用的。
The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android. Note however that the jclass
is a class reference and must be protected with a call toNewGlobalRef
(see the next section).
知道類被卸載以前,這個類的引用,字段的ID,方法的ID都是被保證有效的。發生與類加載器所加載的類中只有被卸載的類才能被垃圾回收器回收的機率,這在android中是不可能的。注意無論這麼樣這jclass做爲一個類的引用且必須調用NewGlobalRef去保護它。
If you would like to cache the IDs when a class is loaded, and automatically re-cache them if the class is ever unloaded and reloaded, the correct way to initialize the IDs is to add a piece of code that looks like this to the appropriate class:
若是你想在一個類被加載的時候緩存它的ID而且在類被卸載後再次加載時自動緩存他們的ID,正確初始化ID的方法是去增長一段代碼到合適的類中
/*
* 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();
}
Create a nativeClassInit
method in your C/C++ code that performs the ID lookups. The code will be executed once, when the class is initialized. If the class is ever unloaded and then reloaded, it will be executed again.
在你的C或者C++代碼中建立一個ntiveClassInit方法
去執行ID的查找工做,在類初始化的時候,這方法將被執行一次。若是這類
曾經被卸載而後從新加載,這個方法將再次被執行。
局部和全局引用
Every argument passed to a native method, and almost every object returned by a JNI function is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid.
幾乎將每個參數傳遞給本地方法而後都經過一個局部引用的JNI函數返回給對象。這意味着在當前線程中的當前的本地方法在這段期間是有效的。過了這段時間,即便這個對象自己在本地方法返回後仍然活着,它也是無效的。
This applies to all sub-classes of jobject
, including jclass
, jstring
, and jarray
. (The runtime will warn you about most reference mis-uses when extended JNI checks are enabled.)
這適用於全部的jobject的子類包括jclass
, jstring
, 和 jarray
. (當啓用擴展的JNI檢測時,在運行時將給出你過多的引用被濫用的警告)
The only way to get non-local references is via the functions NewGlobalRef
and NewWeakGlobalRef
.
這獲得非局部引用的惟一的方法是經過NewGlobalRef
和NewWeakGlobalRef功能函數,
If you want to hold on to a reference for a longer period, you must use a "global" reference. The NewGlobalRef
function takes the local reference as an argument and returns a global one. The global reference is guaranteed to be valid until you call DeleteGlobalRef
.
若是你想在很長一段時間內持有一個引用,你必須適用global應用。NewGlobalRef函數適用一個局部引用做爲參數返回一個全局引用,這全局在你調用
DeleteGlobalRef方法前都被保證是有效的
This pattern is commonly used when caching a jclass returned from FindClass
, e.g.:
在緩存一個來自FindClass方法返回的jclass對象時,這種模式一般是有用的。
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
All JNI methods accept both local and global references as arguments. It's possible for references to the same object to have different values. For example, the return values from consecutive calls to NewGlobalRef
on the same object may be different. To see if two references refer to the same object, you must use theIsSameObject
function. Never compare references with ==
in native code.
全部的JNI方法都接收兩個局部和者全局引用做爲參數。經過同樣的對象去獲得不一樣的值這是合適的。例如,用同樣的對象連續的去調用NewGlobalRef
方法返回的值多是不一樣的。要看是否兩個引用引用同樣的對象,你必須適用theIsSameObject
方法去判斷。在本地代碼中絕對不要適用==去比較。
One consequence of this is that you must not assume object references are constant or unique in native code. The 32-bit value representing an object may be different from one invocation of a method to the next, and it's possible that two different objects could have the same 32-bit value on consecutive calls. Do not use jobject
values as keys.
做爲結論在本地代碼中你不能假設對象的引用是一個常數或者惟一的。一個用32位表示的對象從一個方法調用下一個方法多是不一樣的,它多是兩個不一樣的32位對象在連續的調用。不要適用jobject值做爲鍵
Programmers are required to "not excessively allocate" local references. In practical terms this means that if you're creating large numbers of local references, perhaps while running through an array of objects, you should free them manually with DeleteLocalRef
instead of letting JNI do it for you. The implementation is only required to reserve slots for 16 local references, so if you need more than that you should either delete as you go or use EnsureLocalCapacity
/PushLocalFrame
to reserve more.
程序員須要適當的分配本地引用。在實踐中這表示是否你建立了大量的局部引用,或者當經過一個數組對象運行時,你應該手動的用DeleteLocalRef
釋放他們而不是讓JNI去作這些事。實現只須要你去保存16個局部引用,因此你須要更多的區刪除或者使用EnsureLocalCapacity
/PushLocalFrame
去儲備更多。
Note that jfieldID
s and jmethodID
s are opaque types, not object references, and should not be passed toNewGlobalRef
. The raw data pointers returned by functions like GetStringUTFChars
andGetByteArrayElements
are also not objects. (They may be passed between threads, and are valid until the matching Release call.)
注意jfieldIDs 和jmethodIDs是空類型,不是對象引用,不該該傳遞給NewGlobalRef方法。經過像
GetStringUTFChars
和andGetByteArrayElements
返回的原始數據也不是對象(在被析構以前他們經過在兩個線程間傳遞是有效的。)
One unusual case deserves separate mention. If you attach a native thread with AttachCurrentThread
, the code you are running will never automatically free local references until the thread detaches. Any local references you create will have to be deleted manually. In general, any native code that creates local references in a loop probably needs to do some manual deletion.
一個特別的案例值得單獨說:若是你用AttachCurrentThread附加一個本地線程,你運行的代碼將不會自動的清除局部引用,直到線程死去。你建立的任何局部引用必須手動的刪除。通常來講,在循環中任何本地菜嗎建立的局部引用可能需作一些手動刪除
,(持續更新中)
The Java programming language uses UTF-16. For convenience, JNI provides methods that work with Modified UTF-8 as well. The modified encoding is useful for C code because it encodes \u0000 as 0xc0 0x80 instead of 0x00. The nice thing about this is that you can count on having C-style zero-terminated strings, suitable for use with standard libc string functions. The down side is that you cannot pass arbitrary UTF-8 data to JNI and expect it to work correctly.
If possible, it's usually faster to operate with UTF-16 strings. Android currently does not require a copy inGetStringChars
, whereas GetStringUTFChars
requires an allocation and a conversion to UTF-8. Note thatUTF-16 strings are not zero-terminated, and \u0000 is allowed, so you need to hang on to the string length as well as the jchar pointer.
Don't forget to Release
the strings you Get
. The string functions return jchar*
or jbyte*
, which are C-style pointers to primitive data rather than local references. They are guaranteed valid until Release
is called, which means they are not released when the native method returns.
Data passed to NewStringUTF must be in Modified UTF-8 format. A common mistake is reading character data from a file or network stream and handing it to NewStringUTF
without filtering it. Unless you know the data is 7-bit ASCII, you need to strip out high-ASCII characters or convert them to proper Modified UTF-8 form. If you don't, the UTF-16 conversion will likely not be what you expect. The extended JNI checks will scan strings and warn you about invalid data, but they won't catch everything.
JNI provides functions for accessing the contents of array objects. While arrays of objects must be accessed one entry at a time, arrays of primitives can be read and written directly as if they were declared in C.
To make the interface as efficient as possible without constraining the VM implementation, theGet<PrimitiveType>ArrayElements
family of calls allows the runtime to either return a pointer to the actual elements, or allocate some memory and make a copy. Either way, the raw pointer returned is guaranteed to be valid until the corresponding Release
call is issued (which implies that, if the data wasn't copied, the array object will be pinned down and can't be relocated as part of compacting the heap). You must Release
every array you Get
. Also, if the Get
call fails, you must ensure that your code doesn't try to Release
a NULL pointer later.
You can determine whether or not the data was copied by passing in a non-NULL pointer for the isCopy
argument. This is rarely useful.
The Release
call takes a mode
argument that can have one of three values. The actions performed by the runtime depend upon whether it returned a pointer to the actual data or a copy of it:
0
JNI_COMMIT
JNI_ABORT
One reason for checking the isCopy
flag is to know if you need to call Release
with JNI_COMMIT
after making changes to an array — if you're alternating between making changes and executing code that uses the contents of the array, you may be able to skip the no-op commit. Another possible reason for checking the flag is for efficient handling of JNI_ABORT
. For example, you might want to get an array, modify it in place, pass pieces to other functions, and then discard the changes. If you know that JNI is making a new copy for you, there's no need to create another "editable" copy. If JNI is passing you the original, then you do need to make your own copy.
It is a common mistake (repeated in example code) to assume that you can skip the Release
call if *isCopy
is false. This is not the case. If no copy buffer was allocated, then the original memory must be pinned down and can't be moved by the garbage collector.
Also note that the JNI_COMMIT
flag does not release the array, and you will need to call Release
again with a different flag eventually.
There is an alternative to calls like Get<Type>ArrayElements
and GetStringChars
that may be very helpful when all you want to do is copy data in or out. Consider the following:
jbyte* data = env->GetByteArrayElements(array, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(array, data, JNI_ABORT);
}
This grabs the array, copies the first len
byte elements out of it, and then releases the array. Depending upon the implementation, the Get
call will either pin or copy the array contents. The code copies the data (for perhaps a second time), then calls Release
; in this case JNI_ABORT
ensures there's no chance of a third copy.
One can accomplish the same thing more simply:
env->GetByteArrayRegion(array, 0, len, buffer);
This has several advantages:
Release
after something fails. Similarly, you can use the Set<Type>ArrayRegion
call to copy data into an array, and GetStringRegion
orGetStringUTFRegion
to copy characters out of a String
.
You must not call most JNI functions while an exception is pending. Your code is expected to notice the exception (via the function's return value, ExceptionCheck
, or ExceptionOccurred
) and return, or clear the exception and handle it.
The only JNI functions that you are allowed to call while an exception is pending are:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
Many JNI calls can throw an exception, but often provide a simpler way of checking for failure. For example, ifNewString
returns a non-NULL value, you don't need to check for an exception. However, if you call a method (using a function like CallObjectMethod
), you must always check for an exception, because the return value is not going to be valid if an exception was thrown.
Note that exceptions thrown by interpreted code do not unwind native stack frames, and Android does not yet support C++ exceptions. The JNI Throw
and ThrowNew
instructions just set an exception pointer in the current thread. Upon returning to managed from native code, the exception will be noted and handled appropriately.
Native code can "catch" an exception by calling ExceptionCheck
or ExceptionOccurred
, and clear it withExceptionClear
. As usual, discarding exceptions without handling them can lead to problems.
There are no built-in functions for manipulating the Throwable
object itself, so if you want to (say) get the exception string you will need to find the Throwable
class, look up the method ID for getMessage "()Ljava/lang/String;"
, invoke it, and if the result is non-NULL use GetStringUTFChars
to get something you can hand to printf(3)
or equivalent.
JNI does very little error checking. Errors usually result in a crash. Android also offers a mode called CheckJNI, where the JavaVM and JNIEnv function table pointers are switched to tables of functions that perform an extended series of checks before calling the standard implementation.
The additional checks include:
NewDirectByteBuffer
. Call*Method
JNI call: incorrect return type, static/non-static mismatch, wrong type for ‘this’ (for non-static calls) or wrong class (for static calls). DeleteGlobalRef
/DeleteLocalRef
on the wrong kind of reference. 0
, JNI_ABORT
, orJNI_COMMIT
). (Accessibility of methods and fields is still not checked: access restrictions don't apply to native code.)
There are several ways to enable CheckJNI.
If you’re using the emulator, CheckJNI is on by default.
If you have a rooted device, you can use the following sequence of commands to restart the runtime with CheckJNI enabled:
adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start
In either of these cases, you’ll see something like this in your logcat output when the runtime starts:
D AndroidRuntime: CheckJNI is ON
If you have a regular device, you can use the following command:
adb shell setprop debug.checkjni 1
This won’t affect already-running apps, but any app launched from that point on will have CheckJNI enabled. (Change the property to any other value or simply rebooting will disable CheckJNI again.) In this case, you’ll see something like this in your logcat output the next time an app starts:
D Late-enabling CheckJNI
You can also set the android:debuggable
attribute in your application's manifest to turn on CheckJNI just for your app. Note that the Android build tools will do this automatically for certain build types.