天天看点

Android JNI 数据类型

Java层和Native层都有自己的数据类型,在JNI中,这些数据类型又可以分为基本数据类型和引用数据类型,其中,基本数据类型是可以直接相互转换的,而引用数据类型则需要进行一定的装换才可以。为了方便对两个世界的基本数据类型进行相互装换,JNI为我们提供了一系列的方法来帮助我们完成这些工作。

  1. JNI数据类型
  2. 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)

  1. 基本数据类型

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 */
           
  1. 引用数据类型

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 */
           
  1. 字符串

字符串是引用数据类型中的一种,但却是最常用的引用数据类型,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 	                 设置对象数组元素
Get<PrimitiveType>ArrayElements 	  获取基本数据类型Java数组元素
Release<PrimitiveType>ArrayElements 	回写和释放基本数据类型数组元素
Get<PrimitiveType>ArrayRegion 	       基本数据类型数组拷贝
New <PrimitiveType> Array 	               新建基本数据类型数组
Set<PrimitiveType>ArrayRegion 	       基本数据类型数组回写
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异常

设置对象数组元素

index不在数组范围内会抛出ArrayIndexOutOfBoundsException异常

5.2 基本数据类型数组

5.2.1 Java -> Native转换

获取Java基本数据类型数组元素

该系列函数从Java基本数据类型数组中来获取的元素值,函数返回该数组的元素指针,当操作失败时返回NULL。由于返回的数组元素指针可能是Java数组的副本,所以对返回的数组元素进行操作不一定反映到原始数组上。当传入的isCopy指针不为NULL时,如果函数的返回的是原始数组的拷贝副本,isCopy返回TRUE,否则返回FALSE。执行该函数获取数组元素操作后,需要调用对应的ReleaseArrayElements()做回写和释放操作。

对应的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); 
           

回写和释放数组元素

对当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且不执行回写到原始数组操作

数组拷贝函数

该函数将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/releasearrayelements函数非常相似。如果可能,虚拟机将返回指向原始数组元素的指针;否则,将进行复制。该方法可能使垃圾回收不能执行,该方法可能返回数组的拷贝,因此必须释放此资源。另外,对于如何使用这些函数也有很大的限制。

在调用GetPrimitiveArrayCritical之后,本机代码在调用ReleasePrimitiveArrayCritical之前不可以太多或太耗时的操作。 我们必须将这对函数中的代码视为在“临界区域”中运行。 在临界区域内,本机代码不能调用其他JNI函数,也不能调用可能导致当前线程阻塞并等待另一个Java线程的任何系统调用。