原文出處:http://www.ccbu.cc/index.php/android/android-jni-data-operation.html
在前面關於JNI介紹的文章中我們知道,Java層和Navice層是兩個世界,而JNI正是爲了這個兩個世界能夠友好的相互溝通而設計的。既然是不同的兩個世界,所有他們各自的數據類型定義也是不一樣的,Java層和Native層都有自己的數據類型,在JNI中,這些數據類型又可以分爲基本數據類型和引用數據類型,其中,基本數據類型是可以直接相互轉換的,而引用數據類型則需要進行一定的裝換纔可以。爲了方便對兩個世界的基本數據類型進行相互裝換,JNI爲我們提供了一系列的方法來幫助我們完成這些工作。
1. JNI數據類型
1. 1 基本數據類型
Java類型 | JNI類型 | 類型簽名 |
---|---|---|
boolean | jboolean | Z |
byte | jbyte | B |
char | jchar | C |
short | jshort | S |
int | jint | I |
long | jlong | J |
float | jfloat | F |
double | jdouble | D |
void | void | V |
1.2 引用類型
Java類型 | JNI類型 | 類型簽名 |
---|---|---|
所有對象 | jobject | L+ classname + ; |
Class | jclass | Ljava/lang/Class; |
String | jstring | Ljava/lang/String; |
Object[] | jobjectArray | [L + classname + ; |
boolean[] | jbooleanArray | [Z |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
short[] | jshortArray | [S |
int[] | jintArray | [I |
long[] | jlongArray | [J |
float[] | jfloatArray | [F |
double[] | jdoubleArray | [D |
Throwable | jthrowable | Ljava/lang/Throwable; |
1.3 引用類型的繼承關係
- jobject
- jclass (java.lang.Class objects)
- jstring (java.lang.String objects)
- jarray (arrays)
- jobjectArray (object arrays)
- jbooleanArray (boolean arrays)
- jbyteArray (byte arrays)
- jcharArray (char arrays)
- jshortArray (short arrays)
- jintArray (int arrays)
- jlongArray (long arrays)
- jfloatArray (float arrays)
- jdoubleArray (double arrays)
- jthrowable (java.lang.Throwable objects)
2. 基本數據類型
JNI 中的基本類型和 Java 中的基本類型是直接相互轉換的,實際上,JNI中的這些Java層的基本類型定義就只是對 C/C++ 中的基本類型用 typedef 重新定義了一個新的名字而已。
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
3. 引用數據類型
JNI 把 Java 中所有對象當作一個 C 指針傳遞到本地方法中,這個指針指向 JVM 中的內部數據結構,而內部的數據結構在內存中的存儲方式是不可見得。只能從 JNIEnv 指針指向的函數表中選擇合適的 JNI 函數來操作 JVM 中的數據結構。JNI爲我們提供了一系列的JNI函數來進行引用數據類型的操作和處理。如字符串類型,可以通過JNI函數NewString來在C,C++中創建一個Java字符串。JNI中關於這些引用數據類型定義的源碼如下。
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#endif /* not __cplusplus */
4. 字符串
字符串是引用數據類型中的一種,但卻是最常用的引用數據類型,JNI專門爲字符串提供了一系列的函數,相關的函數主要有如下一些。
函數 | 說明 |
---|---|
jstring NewString(const jchar* unicodeChars, jsize len) | 新建String對象 |
jsize GetStringLength(jstring string) | 獲取Java字符串的長度 |
const jchar* GetStringChars(jstring string, jboolean* isCopy) | 從Java字符串獲取字符數組 |
void ReleaseStringChars(jstring string, const jchar* chars) | 釋放從Java字符串中獲取的字符數組 |
jstring NewStringUTF(const char* bytes) | 新建UTF-8字符串 |
jsize GetStringUTFLength(jstring string) | 獲取UTF-8字符串的長度 |
const char* GetStringUTFChars(jstring string, jboolean* isCopy) | 獲取Java UTF-8字符串的字符數組 |
void ReleaseStringUTFChars(jstring string, const char* utf) | 釋放從UTF-8字符串中獲取的字符數組 |
void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf) | 從Java字符串中截取一段字符 |
void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf) | 從UTF-8字符串中截取一段字符 |
const jchar* GetStringCritical(jstring string, jboolean* isCopy) | 獲取原始字符串的直接指針 |
void ReleaseStringCritical(jstring string, const jchar* carray) | 釋放原始字符串指針 |
4.1 Java -> Native轉換
在上述方法中,一般使用以下兩對方法來實現將字符串從Java層轉爲Native層的類型。
const jchar* GetStringChars(jstring string, jboolean* isCopy)
void ReleaseStringChars(jstring string, const jchar* chars)
const char* GetStringUTFChars(jstring string, jboolean* isCopy)
void ReleaseStringUTFChars(jstring string, const char* utf)
- 一般,使用
GetStringChars
函數和GetStringUTFChars
來從Java字符串中來獲取Native字符數組;獲取到的字符串數組使用完以後,需要分別調用ReleaseStringChars
和ReleaseStringUTFChars
來進行釋放操作。 - isCopy這個參數很重要,這是一個指向Java布爾類型的指針。函數返回之後應當檢查這個參數的值,如果值爲JNI_TRUE表示返回的字符是Java字符串的拷貝,我們可以對其中的值進行任意修改。如果返回值爲JNI_FALSE,表示這個字符指針指向原始Java字符串的內存,這時候對字符數組的任何修改都將會原始字符串的內容。如果你不關係字符數組的來源,或者說你的操作不會對字符數組進行任何修改,可以傳入NULL。
4.2 Native ->Java 轉換
反過來,當我們需要將Native層的字符串數組傳遞到Java層的時候,可以使用以下兩個函數來創建Java字符串。
jstring NewString(const jchar* unicodeChars, jsize len)
jstring NewStringUTF(const char* bytes)
上面兩個函數返回的是一個局部引用值,如果不是直接返回了上面創建的字符串,我們需要調用DeleteLocalRef函數來主動釋放。
void DeleteLocalRef(jobject localRef)
雖然局部引用類型的值在在native方法返回後會自動釋放,但JNI中局部引用表(local reference table)是有大小限制的,一般爲512,所以如果在你的函數中,通過使用循環或者遞歸調用等形式創建太多局部引用值,當創建的個數大於局部引用表的大小時,就會造成局部引用表溢出,從而導致程序崩潰。
4.3 異常
在上述的字符串相關的操作函數中,NewString
,GetStringChars
等函數在內存不足的情況下會拋出OutOfMemoryError
並返回NULL值。GetStringRegion
會拋出StringIndexOutOfBoundsException
異常。JNI 的異常和 Java 中的異常處理流程是不一樣的,Java 遇到異常如果沒有捕獲,程序會立即停止運行。而 JNI 遇到未決的異常不會改變程序的運行流程,也就是程序會繼續往下走,這樣後面針對這個字符串的所有操作都是非常危險的。
5. 數組類型
JNI 中的對象數組是引用數據類型的一種,和字符串操作一樣,不能直接去訪問 Java 傳遞給 JNI 層的數組,必須使用JNI 提供的數組操作函數來訪問和設置 Java 層的數組對象。
函數 | 說明 |
---|---|
GetArrayLength | 獲取數組長度 |
NewObjectArray | 新建對象數組 |
GetObjectArrayElement | 獲取對象數組元素 |
SetObjectArrayElement | 設置對象數組元素 |
GetArrayElements | 獲取基本數據類型Java數組元素 |
ReleaseArrayElements | 回寫和釋放基本數據類型數組元素 |
GetArrayRegion | 基本數據類型數組拷貝 |
New Array | 新建基本數據類型數組 |
SetArrayRegion | 基本數據類型數組回寫 |
GetPrimitiveArrayCritical | 獲取基本數據類型數組原始指針 |
ReleasePrimitiveArrayCritical | 釋放基本數據類型數組原始指針 |
5.1 數組對象方法
- 獲取數組長度
jsize GetArrayLength(JNIEnv *env, jarray array)
- 新建對象數組
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement)
內存不足時會拋出OutOfMemoryError異常
- 獲取對象數組元素
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index)
index不在數組範圍內會拋出ArrayIndexOutOfBoundsException異常
- 設置對象數組元素
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value)
index不在數組範圍內會拋出ArrayIndexOutOfBoundsException異常
5.2 基本數據類型數組
5.2.1 Java -> Native轉換
- 獲取Java基本數據類型數組元素
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
該系列函數從Java基本數據類型數組中來獲取的元素值,函數返回該數組的元素指針,當操作失敗時返回NULL。由於返回的數組元素指針可能是Java數組的副本,所以對返回的數組元素進行操作不一定反映到原始數組上。當傳入的isCopy
指針不爲NULL時,如果函數的返回的是原始數組的拷貝副本,isCopy
返回TRUE,否則返回FALSE。執行該函數獲取數組元素操作後,需要調用對應的Release<PrimitiveType>ArrayElements()
做回寫和釋放操作。
對應的Java基本數據類型數組元素的獲取函數如下。
jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy);
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy);
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy);
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy);
jint* GetIntArrayElements(jintArray array, jboolean* isCopy) ;
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy);
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy);
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy);
- 回寫和釋放數組元素
void Release<PrimitiveType>ArrayElements(JNIEnv env, ArrayType array, NativeType elems, jint mode)
對當Native層不在需要對從Java數組中獲取到的元素進行操時,可以通過該系列的函數來做回寫和釋放操作。對應的回寫和釋放ArrayElements的函數如下。
void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, jint mode);
void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, jint mode);
void ReleaseCharArrayElements(jcharArray array, jchar* elems, jint mode);
void ReleaseShortArrayElements(jshortArray array, jshort* elems, jint mode);
void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode);
void ReleaseLongArrayElements(jlongArray array, jlong* elems, jint mode);
void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, jint mode);
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, jint mode);
當需要時,此函數可以將拷貝副本的操作變化同步得到原始Java數組。mode
參數可以指定以何種方式來執行回寫和釋放操作。如果elems
本身並原始數組的不是一個副本,則mode
參數不論是什麼值都是無效的。mode
的定義如下。
數組 | 說明 |
---|---|
0 | 回寫到原始數組並釋放elems |
JNI_COMMIT | 回寫到原始數組但不釋放elems |
JNI_ABORT | 釋放elems且不執行回寫到原始數組操作 |
- 數組拷貝函數
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
該函數將Java數組指定範圍內的原始拷貝到指定的緩存區,如果指定的index範圍超出了有效範圍,會拋出ArrayIndexOutOfBoundsException
異常。buf
是在c++層預先開闢好的緩衝區,函數將Java數組指定範圍內的原始拷貝到該緩衝區內。對應的具體的基本數據類型數組的GetArrayRegion
如下。
void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf);
void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf);
void GetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf);
void GetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf);
void GetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf);
void GetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf);
void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf);
void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf);
5.2.2 Native ->Java轉換
-
新建基本數據類型數組
ArrayType New <PrimitiveType> Array(JNIEnv *env, jsize length);
創建Java基本數據類型數組對象,當創建失敗時返回NULL。具體的Java基本數據類型數組創建函數如下。
jbooleanArray NewBooleanArray(jsize length) ; jbyteArray NewByteArray(jsize length); jcharArray NewCharArray(jsize length); jshortArray NewShortArray(jsize length); jintArray NewIntArray(jsize length); jlongArray NewLongArray(jsize length); jfloatArray NewFloatArray(jsize length); jdoubleArray NewDoubleArray(jsize length);
-
數組回寫函數
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, const NativeType *buf);
該系列函數將指定範圍內的本地緩衝區buf
中的元素回寫到Java元素數組中,如果指定的index範圍超出了有效範圍,會拋出ArrayIndexOutOfBoundsException
異常。
對應的具體基本數據類型的數組回寫操作函數如下。
void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf) ;
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf);
void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf) ;
void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf);
void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf);
void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf) ;
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf);
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf)
5.3 操基本數據類型數組原始指針
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
這兩個函數的與上面的 get/release<primitivetype>arrayelements
函數非常相似。如果可能,虛擬機將返回指向原始數組元素的指針;否則,將進行復制。該方法可能使垃圾回收不能執行,該方法可能返回數組的拷貝,因此必須釋放此資源。另外,對於如何使用這些函數也有很大的限制。
在調用GetPrimitiveArrayCritical
之後,本機代碼在調用ReleasePrimitiveArrayCritical
之前不可以太多或太耗時的操作。 我們必須將這對函數中的代碼視爲在“臨界區域”中運行。 在臨界區域內,本機代碼不能調用其他JNI函數,也不能調用可能導致當前線程阻塞並等待另一個Java線程的任何系統調用。