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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容