望远山,知近路,而后自得其乐!

Android JNI开发详解(4)-数据操作

在前面关于JNI介绍的文章中我们知道,Java层和Navice层是两个世界,而JNI正是为了这个两个世界能够友好的相互沟通而设计的。既然是不同的两个世界,所有他们各自的数据类型定义也是不一样的,Java层和Native层都有自己的数据类型,在JNI中,这些数据类型又可以分为基本数据类型和引用数据类型,其中,基本数据类型是可以直接相互转换的,而引用数据类型则需要进行一定的装换才可以。为了方便对两个世界的基本数据类型进行相互装换,JNI为我们提供了一系列的方法来帮助我们完成这些工作。

1. JNI数据类型

1. 1 基本数据类型

Java类型JNI类型类型签名
booleanjbooleanZ
bytejbyteB
charjcharC
shortjshortS
intjintI
longjlongJ
floatjfloatF
doublejdoubleD
voidvoidV

1.2 引用类型

Java类型JNI类型类型签名
所有对象jobjectL+ classname + ;
ClassjclassLjava/lang/Class;
StringjstringLjava/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
ThrowablejthrowableLjava/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设置对象数组元素
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异常

  • 设置对象数组元素
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线程的任何系统调用。

文章评论已关闭!