原文出处:http://www.ccbu.cc/index.php/android/android-jni-data-operation.html
在前面关于JNI介绍的文章中我们知道,Java层和Navice层是两个世界,而JNI正是为了这个两个世界能够友好的相互沟通而设计的。既然是不同的两个世界,所有他们各自的数据类型定义也是不一样的,Java层和Native层都有自己的数据类型,在JNI中,这些数据类型又可以分为基本数据类型和引用数据类型,其中,基本数据类型是可以直接相互转换的,而引用数据类型则需要进行一定的装换才可以。为了方便对两个世界的基本数据类型进行相互装换,JNI为我们提供了一系列的方法来帮助我们完成这些工作。
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 |
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; |
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 */
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 */
字符串是引用数据类型中的一种,但却是最常用的引用数据类型,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) | 释放原始字符串指针 |
在上述方法中,一般使用以下两对方法来实现将字符串从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
来进行释放操作。反过来,当我们需要将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,所以如果在你的函数中,通过使用循环或者递归调用等形式创建太多局部引用值,当创建的个数大于局部引用表的大小时,就会造成局部引用表溢出,从而导致程序崩溃。
在上述的字符串相关的操作函数中,NewString
,GetStringChars
等函数在内存不足的情况下会抛出OutOfMemoryError
并返回NULL值。GetStringRegion
会抛出StringIndexOutOfBoundsException
异常。JNI 的异常和 Java 中的异常处理流程是不一样的,Java 遇到异常如果没有捕获,程序会立即停止运行。而 JNI 遇到未决的异常不会改变程序的运行流程,也就是程序会继续往下走,这样后面针对这个字符串的所有操作都是非常危险的。
JNI 中的对象数组是引用数据类型的一种,和字符串操作一样,不能直接去访问 Java 传递给 JNI 层的数组,必须使用JNI 提供的数组操作函数来访问和设置 Java 层的数组对象。
函数 | 说明 |
---|---|
GetArrayLength | 获取数组长度 |
NewObjectArray | 新建对象数组 |
GetObjectArrayElement | 获取对象数组元素 |
SetObjectArrayElement | 设置对象数组元素 |
GetArrayElements | 获取基本数据类型Java数组元素 |
ReleaseArrayElements | 回写和释放基本数据类型数组元素 |
GetArrayRegion | 基本数据类型数组拷贝 |
New Array | 新建基本数据类型数组 |
SetArrayRegion | 基本数据类型数组回写 |
GetPrimitiveArrayCritical | 获取基本数据类型数组原始指针 |
ReleasePrimitiveArrayCritical | 释放基本数据类型数组原始指针 |
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异常
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
该系列函数从Java基本数据类型数组中来获取的元素值,函数返回该数组的元素指针,当操作失败时返回NULL。由于返回的数组元素指针可能是Java数组的副本,所以对返回的数组元素进行操作不一定反映到原始数组上。当传入的isCopy
指针不为NULL时,如果函数的返回的是原始数组的拷贝副本,isCopy
返回TRUE,否则返回FALSE。执行该函数获取数组元素操作后,需要调用对应的Release
做回写和释放操作。
对应的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);
新建基本数据类型数组
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)
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
这两个函数的与上面的 get/release
函数非常相似。如果可能,虚拟机将返回指向原始数组元素的指针;否则,将进行复制。该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。另外,对于如何使用这些函数也有很大的限制。
在调用GetPrimitiveArrayCritical
之后,本机代码在调用ReleasePrimitiveArrayCritical
之前不可以太多或太耗时的操作。 我们必须将这对函数中的代码视为在“临界区域”中运行。 在临界区域内,本机代码不能调用其他JNI函数,也不能调用可能导致当前线程阻塞并等待另一个Java线程的任何系统调用。