Android JNI開發詳解(4)-數據操作

原文出處: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字符數組;獲取到的字符串數組使用完以後,需要分別調用ReleaseStringCharsReleaseStringUTFChars來進行釋放操作。
  • 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 異常

在上述的字符串相關的操作函數中,NewStringGetStringChars等函數在內存不足的情況下會拋出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線程的任何系統調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章