JNI/NDK入门指南之JNI访问数组
Android JNI/NDK入门指南目录
JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用
JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二
在前面的章节JNI/NDK入门指南之JNI字符串处理中讲解了JNI对字符串的处理流程。今天我们继续向JNI的知识海洋进军讲解JNI对数组的处理。本章内容有点多哦!
前言
JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 Java 层的数组对象。阅读此文假设你已经了解了 JNI 与 Java 数据类型的映射关系,如果还不了解的,请仔细阅读篇章JNI/NDK入门指南之JNI数据类型,描述符详解,里面有详尽的解析。下面让我们从JNI的基本数组操作函数开始,一步步带领读者攻破JNI对数组的处理。
一. 初探JNI数组处理函数
好了有了前面知识的铺垫,下面让我们来看看JNIEnv为我们提供了那些常见的数组处理函数。
1.1 GetArrayLength
函数原型: jsize (GetArrayLength)(JNIEnv env, jarray array);
函数功能: 返回数组中的元素个数。
参数:
- env: JNIEnv接口指针
- array: Java通过JNI传递的Java数组对象
返回值: 数组的长度
1.2 NewObjectArray
函数原型: jobjectArray NewObjectArray (JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
函数功能: 构建JNI引用类型的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement,一般使用NULL就好。
参数:
- env: JNIEnv接口指针
- lenght: 数组大小
- elementClass: 引用数组元素类
- initialElement:初始值。通常为NULL
返回值: 返回需要创建的JNI引用类型数组对象。如果无法构造数组,则为NULL。
异常抛出: 如果系统内存不足,则抛出OutOfMemoryError异常
1.3 GetObjectArrayElement和SetObjectArrayElement
这对函数主要的功能是获取/设置数组元素的内容,下面我们分别对其介绍之:
GetObjectArrayElement
函数原型: jobject GetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index)
函数功能: 返回jobjectArray数组的元素,通常是获取JNI引用类型数组元素
参数:
- env: JNIEnv接口指针
- array: 通过JNI传递到C/C++层的Java引用数组
- index: 数组下标
返回值:返回数组中index下标的对象
异常抛出: 如果 index 不是数组中的有效下标,则抛出ArrayIndexOutOfBoundsException异常
SetObjectArrayElement
函数原型: void SetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index, jobject value)
函数功能: 设置jobjectArray数组中index下标对象的值
参数:
-
env: JNIEnv接口指针
-
array:通过JNI传递到C/C++层的Java引用数
-
index: 数组下表值
-
value: 将要设置的新值
异常抛出: ArrayIndexOutOfBoundsException:如果 index 不是数组中的有效下标。 ArrayStoreException:如果 value 的类不是数组元素类的子类。
1.4 New<PrimitiveType>Array函数集
这里的PrimitiveType指代的是一系列的JNI基本数据类型。
函数原型: NativeTypeArray New<PrimitiveType>Array (JNIEnv* env, jsize size)
函数功能: 用于构造JNI基本类型数组对象的一系列操作。
使用说明: 在下表中会将特定的基本类型数组构造函数及其返回值一一对应。在实际应用中把PrimitiveType替换为某个实际的基本类型数据类型,然后再将NativeType替换成对应的JNI Native Type即可,。譬如我们以int型来说明,那么其对应的函数为:
jintArray (*NewIntArray)(JNIEnv*, jsize);
参数:
- env: JNIEnv接口指针
- lenght: 需要创建的数组的长度
返回值:返回JNI基本数据类型数组。如果无法构造该数组,则为 NULL。
NewPrimitiveTypeArray函数名 | NativeTypeArray 返回数组 |
---|---|
NewBooleanArray() | jbooleanArray |
NewByteArray() | jbyteArray |
NewCharArray() | jcharArray |
NewShortArray() | jshorArray |
NewIntArray() | jintArray |
NewLongArray | jlongArray |
NewFloatArray() | jfloatArray |
NewDoubleArray | jdoubleArray |
1.5 Get/Release<PrimitiveType>ArrayElements函数集
这里的PrimitiveType指代的是一系列的JNI基本数据类型,这对函数一般都是成对使用,下面我们分别进行分析:
Get<PrimitiveType>ArrayElements
函数原型: NativeType* Get<PrimitiveType>ArrayElements(JNIEnv *env, NativeTypeArray array, jboolean *isCopy)
函数功能: 一组返回JNI基本数据类型数组的函数。结果在调用相应的 ReleasePrimitiveTypeArrayElements()函数前将一直有效。
使用说明: 在实际使用过程中将PrimitiveType替换成某个实际的基本类型元素访问函数,然后再将NativeType替换成对应的JNI Native Type即可,可以参见下面的表格。譬如我们以int型来说明,那么其对应的函数为:
jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
参数:
- env:JNI 接口指针
- array:Java基本类型数组对象
- isCopy:指向布尔值的指针 ,该参数表示返回的数组指针是原始数组,还是拷贝原始数据到临时缓冲区的指针,如果是 JNI_TRUE:表示临时缓冲区数组指针,JNI_FALSE:表示临时原始数组指针
返回值: 返回指向数组元素的指针,如果操作失败,则为 NULL。
Get<PrimitiveType>ArrayElements函数集 | NativeTypeArray 数组类型 | NativeType |
---|---|---|
GetBooleanArrayElements() | jbooleanArray | jboolean |
GetByteArrayElements() | jbyteArray | jbyte |
GetCharArrayElements() | jcharArray | jchar |
GetShortArrayElements() | jshortArray | jshort |
GetIntArrayElements() | jintArray | jint |
GetLongArrayElements() | jlongArray | jlong |
GetFloatArrayElements() | jfloatArray | jfloat |
GetDoubleArrayElements() | jdoubleArray | jdouble |
Release<PrimitiveType>ArrayElements
函数原型: void Release<PrimitiveType>ArrayElements (JNIEnv *env, NativeTypeArray array, NativeType *elems,jint mode);
函数功能: 通知虚拟机平台相关代码无需再访问 elems 的一组函数。elems 参数是一个通过使用对应的GetPrimitiveTypeArrayElements() 函数由 array 导出的指针。必要时,该函数将把对 elems 的修改复制回基本类型数组。mode参数将提供有关如何释放数组缓冲区的信息。如果elems 不是 array 中数组元素的副本,mode将无效。
否则,mode 将具有下表所述的功能:
模式 | 功能 |
---|---|
0 | 更新数组并释放elems 缓冲区 |
JNI_COMMIT | 更新但不释放elems 缓冲区 |
JNI_ABORT | 不作更新但释放elems缓冲区 |
通常开发情况下,编程人员将把“0”传给 mode 参数以确保数组内容保持一致。其它选项可以使编程人员进一 步控制内存管理,但使用时务必慎重。
使用说明: 将PrimitiveType替换成对应的数据类型,将NativeType 替换成JNI Native type类型,譬如我们以释放int数组为例:
void (*ReleaseIntArrayElements)(JNIEnv*, jintArray,
jint*, jint);
参数:
- env:JNIENV接口指针
- array:Java 基本类型数组对象
- elems:指向数组元素的指针
- mode:释放模式
Relase<PrimitiveType>ArrayElements函数集 | NativeTypeArray 数组类型 | NativeType |
---|---|---|
ReleaseBooleanArrayElements() | jbooleanArray | jboolean |
ReleaseByteArrayElements() | jbyteArray | jbyte |
ReleaseCharArrayElements() | jcharArray | jchar |
ReleaseShortArrayElements() | jshortArray | jshort |
ReleaseIntArrayElements() | jintArray | jint |
ReleaseLongArrayElements() | jlongArray | jlong |
ReleaseFloatArrayElements() | jfloatArray | jfloat |
ReleaseDoubleArrayElements() | jdoubleArray | jdouble |
1.6 Get/Set<PrimitiveType>ArrayRegion函数集
这里的PrimitiveType指代的是一系列的JNI基本数据类型,这对函数集分别用来将基本类型数组某一区域复制到缓冲区中的一组函数/将基本类型数组的某一区域从缓冲区中复制回来的一组函数,下面让我们来分别分析:
Get<PrimitiveType>ArrayRegion
函数原型:void Set<PrimitiveType>ArrayRegion (JNIEnv *env, NativeTypeArray array, jsize start, jsize len, NativeType *buf);
函数功能:将基本类型数组某一区域复制到缓冲区中的一组函数。
使用说明: 将PrimitiveType替换成对应的数据类型,将NativeType 替换成JNI Native type类型,譬如我们以释放int数组为例:
void (*GetIntArrayRegion)(JNIEnv*, jintArray,
jsize, jsize, jint*);
参数:
- env:JNIEnv 接口指针
- array: Java基本类型数组,源缓冲区
- start:起始下标
- len:要复制的元素长度
- buf:目的缓冲区
异常抛出: 如果区域中的某个下标无效,将抛出ArrayIndexOutOfBoundsException异常。
函数集如下:
Get<PrimitiveType>ArrayRegion函数集 | NativeTypeArray 数组类型 | NativeType |
---|---|---|
GetBooleanArrayRegion() | jbooleanArray | jboolean |
GetByteArrayRegion() | jbyteArray | jbyte |
GetCharArrayRegion() | jcharArray | jchar |
GetShortArrayRegion() | jshortArray | jshort |
GetIntArrayRegion() | jintArray | jint |
GetLongArrayRegion() | jlongArray | jlong |
GetFloatArrayRegion() | jfloatArray | jfloat |
GetDoubleArrayRegion() | jdoubleArray | jdouble |
Set<PrimitiveType>ArrayRegion
函数原型:void Set<PrimitiveType>ArrayRegion (JNIEnv *env, NativeTypeArray array, jsize start, jsize len, const NativeType *buf);
函数功能:将缓冲区中的内容复制到基本数据类型数组的某一区域,有点类似memcpy函数功能。
使用说明: 将PrimitiveType替换成对应的数据类型,将NativeType 替换成JNI Native type类型,譬如我们以释放int数组为例:
void (*GetIntArrayRegion)(JNIEnv*, jintArray,
jsize, jsize, jint*);
参数:
- env:JNIEnv 接口指针
- array: Java基本类型数组 ,目的缓冲区
- start:起始下标
- len:要复制的元素长度
- buf:源缓冲区
异常抛出: 如果区域中的某个下标无效,将抛出ArrayIndexOutOfBoundsException异常。
函数集如下:
SetArrayRegion函数集 | NativeTypeArray 数组类型 | NativeType |
---|---|---|
SetBooleanArrayRegion() | jbooleanArray | jboolean |
SetByteArrayRegion() | jbyteArray | jbyte |
SetCharArrayRegion() | jcharArray | jchar |
SetShortArrayRegion() | jshortArray | jshort |
SetIntArrayRegion() | jintArray | jint |
SetLongArrayRegion() | jlongArray | jlong |
SetFloatArrayRegion() | jfloatArray | jfloat |
SetDoubleArrayRegion() | jdoubleArray | jdouble |
二. 再探JNI数组处理实战分析
前面的章节,我们将JNI中有关数组处理的函数基本逐个分析,一网打净了(当然是夸张说法了)。说得再多不练,都是纸上谈兵,下面我们来实战一把,跟紧我要开战了,可别走丢了。
2.1 访问和返回基本类型一维数组
这里我们以int型数组为例来说明,JNI中访问和返回基本类型一维数组。
Java端代码:
package com.xxx.jni;
public class JNIArrayManager {
public native int[] operateIntArray(int[] array);// 一维数组入参和作为返回值
static {
System.loadLibrary("jniArray");
}
}
private void operateIntArray() {
int[] array_int = { 10, 100 };
JNIArrayManager jniArrayManager = new JNIArrayManager();
int[] array_out = jniArrayManager.operateIntArray(array_int);
if (array_out != null) {
for (int i = 0; i < array_out.length; i++) {
Log.e("JNI_ARRAY", "operateIntArray : " + array_out[i]);
}
}
}
JNI端代码:
com_xxx_jni_JNIArrayManager.h文件代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_jni_JNIArrayManager */
#ifndef _Included_com_xxx_jni_JNIArrayManager
#define _Included_com_xxx_jni_JNIArrayManager
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xxx_jni_JNIArrayManager
* Method: operateIntArray
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateIntArray
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif
com_xxx_jni_JNIArrayManager.cpp实现代码如下:
#include "com_xxx_jni_JNIArrayManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#define TAG "JNI_ARRAY"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
JNIEXPORT jintArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateIntArray
(JNIEnv * env, jobject object, jintArray intArray_in)
{
/********** 解析从Java Native得到的jintArray数组 **********/
jint * intArray;
//操作方式一:
// 如同 getUTFString 一样,会申请 native 内存
intArray= env->GetIntArrayElements(intArray_in, NULL);
if(intArray == NULL){
return NULL;
}
//得到数组的长度
const int length = env->GetArrayLength(intArray_in);
LOGE(TAG,"The intArray length is : %d\n", length);
for(int i = 0; i < length; i++)
{
LOGE(TAG,"The intArray_in[%d} is %d\n", i,intArray[i]);
}
//操作方法二
if(length > 0)
{
jint * buf_in;//直接定义一个jni型数组
buf_in = (int *)malloc(sizeof(int) * length);
//通过GetIntArrayRegion方法获取数组内容,此种方法不会申请内存
env->GetIntArrayRegion(intArray_in, 0, length, buf_in);
free(buf_in);
}
//对于操作方式一,使用完了一定不要忘了释放内存
env->ReleaseIntArrayElements(intArray_in, intArray, 0);
/**********从JNI返回jintArray数组给Java层**********/
jintArray intArray_out;//返回给java
const int len_out = 10;
intArray_out = env->NewIntArray(len_out);//在Native层创建一个长度为10的int型数组
jint buf_out[len_out] = {0};
for(int j = 0; j < len_out; j++){
buf_out[j] = j * 2;
}
//使用SetIntArrayRegion来赋值
env->SetIntArrayRegion(intArray_out, 0, len_out, buf_out);
return intArray_out;
}
运行演示:
λ adb shell logcat -s JNI_ARRAY
--------- beginning of system
--------- beginning of main
I/JNI_ARRAY( 6628): The intArray length is : 2
I/JNI_ARRAY( 6628): The intArray_in[0} is 10
I/JNI_ARRAY( 6628): The intArray_in[1} is 100
E/JNI_ARRAY( 6628): operateIntArray : 0
E/JNI_ARRAY( 6628): operateIntArray : 2
E/JNI_ARRAY( 6628): operateIntArray : 4
E/JNI_ARRAY( 6628): operateIntArray : 6
E/JNI_ARRAY( 6628): operateIntArray : 8
E/JNI_ARRAY( 6628): operateIntArray : 10
E/JNI_ARRAY( 6628): operateIntArray : 12
E/JNI_ARRAY( 6628): operateIntArray : 14
E/JNI_ARRAY( 6628): operateIntArray : 16
E/JNI_ARRAY( 6628): operateIntArray : 18
案例分析:
在本例中,Java类JNIArrayManager 中定义了一个operateIntArray的Native方法,参数类型是int[]并且返回值类型也是int[],对应JNI中的jnitArray类型。下面让我们对本地代码以一一分析。
解析jintArray数组数据:
在该部分代码中我们将会解析通过JNI传递下来的Java的int型数组内容,并打印出来。
(1) 首先通过 JNI的GetArrayLength 函数获取数组的长度,已知数组是 jintArray 类型,可以得出数组的元素类型是 jint,然后根据数组的长度和数组元素类型,申请相应大小的缓冲区。如果缓冲区不大的话,当然也可以直接在栈上申请内存,那样效率更高,但是没那么灵活,因为 Java 数组的大小变了,本地代码也跟着修改。
(2) 使用操作方式一GetIntArrayElements获取jintArray数据,其中该函数第三个参数表示返回的数组指针是原始数组,还是拷贝原始数组到临时缓冲区的指针,如果是JNI_TRUE则表示临时缓冲区数组指针,如果是JNI_FALSE临时原始数组指针。在实际开发当中,如果我们想修改原来的数组数据可以通过修改这个参数的值来达到。对于通过该函数获取的指针必须做校验,因为当原始数据在内存当中不是连续存放的情况下,JVM 会复制所有原始数据到一个临时缓冲区,并返回这个临时缓冲区的指针。有可能在申请开辟临时缓冲区内存空间时,会内存不足导致申请失败,这时会返回 NULL。并且使用该函数后,必须使用ReleaseIntArrayElements进行内存释放,不然可能会导致内存泄漏问题。
(3) 使用操作方式二GetIntArrayRegion 函数将 Java中int型数组中的所有元素拷贝到 C 缓冲区中,最后释放存储 java中int型数组元素的 缓冲区。关于GetIntArrayRegion的具体使用可以参见前面章节的内容。JNI 还提供了一个和 GetIntArrayRegion 相对应的函 SetIntArrayRegion,本地代码可以通过这个函数来修改所有基本数据类型数组的元素。
返回jintArray数组数据:
该部分代码会在JNI的本地方法中创建int型数组并赋值,然后返回给Java端。
(1) 定义jintArray类型变量intArray_out,此时并没有赋值和创建。
(2) 调用NewIntArray函数创建长度为intArray_out的int型数组对象,关于该函数的具体使用参见章节1.4 New<PrimitiveType>Array函数集。
(3) 接着调用SetIntArrayRegion将本地方法缓冲区中buf_out数据复制到intArray_out中。
(4) 最后直接将intArray_out返回给Java。
小结:
上面的章节我们通过一个访问和返回一维数组的实例,讲解了JNI中怎么处理数组的。那么下面让我们总结一下前面两种方法的优缺点和使用范围:
-
对于小量的、固定大小的数组,应该选择 Get/SetArrayRegion 函数来操作数组元素是效率最高的。因为这对函数要求提前分配一个 C 临时缓冲区来存储数组元素,你可以直接在 Stack(栈)上或用 malloc 在堆上来动态申请,当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝一份到临时缓冲区才能访问而觉得效率低?我想告诉你的是,像这种复制少量数组元素的代价是很小的,几乎可以忽略。这对函数的另外一个优点就是,允许你传入一个开始索引和长度来实现对子数组元素的访问和操作(SetArrayRegion函数可以修改数组),不过传入的索引和长度不要越界,函数会进行检查,如果越界了会抛出 ArrayIndexOutOfBoundsException 异常。
-
Get/ReleaseArrayElements 系列函数永远是安全的,虚拟机 会选择性的返回一个指针,这个指针可能指向原始数据,也可能指向原始数据的复制。
通过这个访问基本类型int一维数组的实例,也可以将这个模式代码套用到其它的基本类型一维数组里面,各位读者看官朋友们可以自行扩展验证!
2.2 访问和返回String类型一维数组
在前面的章节里面我们以int型数组为例讲解,在本章节中我们以String类型引用数组为例,讲解JNI中怎么访问和返回String类型一维数组。
Java端代码:
package com.xxx.jni;
public class JNIArrayManager {
public native String[] operateStringArrray(String[] array);//String数组入参和做为返回值
static {
System.loadLibrary("jniArray");
}
}
private void operateStringArray(){
JNIArrayManager jniArrayManager = new JNIArrayManager();
String[] string_in = {"Hello","JNI"};
String[] string_out = jniArrayManager.operateStringArrray(string_in);
for(int i = 0; i < string_out.length; i++){
Log.e("JNI_ARRAY","operateStringArray : " + string_out[i].toString());
}
}
JNI端代码:
com_xxx_jni_JNIArrayManager.h文件代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_jni_JNIArrayManager */
#ifndef _Included_com_xxx_jni_JNIArrayManager
#define _Included_com_xxx_jni_JNIArrayManager
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xxx_jni_JNIArrayManager
* Method: operateStringArrray
* Signature: ([Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateStringArrray
(JNIEnv *, jobject, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
com_xxx_jni_JNIArrayManager.cpp实现代码如下:
#include "com_xxx_jni_JNIArrayManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#define TAG "JNI_ARRAY"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
{
(env)->ExceptionDescribe();
(env)->ExceptionClear();
//printf("jstringToNative函数转换时,传入的参数str为空");
return NULL;
}
jbyteArray bytes = 0;
jthrowable exc;
char *result = 0;
if ((env)->EnsureLocalCapacity(2) < 0)
{
return 0; /* out of memory error */
}
jclass jcls_str = (env)->FindClass("java/lang/String");
jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B");
bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes);
exc = (env)->ExceptionOccurred();
if (!exc)
{
jint len = (env)->GetArrayLength( bytes);
result = (char *)malloc(len + 1);
if (result == 0)
{
//JNU_ThrowByName( "java/lang/OutOfMemoryError", 0);
(env)->DeleteLocalRef(bytes);
return 0;
}
(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result);
result[len] = 0; /* NULL-terminate */
}
else
{
(env)->DeleteLocalRef( exc);
}
(env)->DeleteLocalRef( bytes);
return (char*)result;
}
//将char * 转装成jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{
//定义java String类 strClass
jclass strClass = (env)->FindClass("java/lang/String");
//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
//将char* 转换为byte数组
(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
//设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF( "utf-8");
//将byte数组转换为java String,并输出
return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding);
}
/*
* Class: com_xxx_jni_JNIArrayManager
* Method: operateStringArrray
* Signature: ([Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateStringArrray
(JNIEnv * env, jobject object, jobjectArray objectArray_in)
{
/*******获取从JNI传过来的String数组数据**********/
jsize size = env->GetArrayLength(objectArray_in);
for(int i = 0; i < size; i++)
{
jstring string_in= (jstring)env->GetObjectArrayElement(objectArray_in, i);
char *char_in = jstringToNative(env, string_in);
LOGE(TAG,"objectArray_in[%d] : %s\n", i, char_in);
}
/***********从JNI返回String数组给Java层**************/
jclass clazz = env->FindClass("java/lang/String");
jobjectArray objectArray_out;
const int len_out = 5;
objectArray_out = env->NewObjectArray(len_out, clazz, NULL);
char * char_out[]= { "Hello,", "world!", "JNI", "is", "fun" };
jstring temp_string;
for( int i= 0; i < len_out; i++ )
{
temp_string = nativeTojstring(env, char_out[i]);
env->SetObjectArrayElement(objectArray_out, i, temp_string);
}
return objectArray_out;
}
运行演示:
λ adb shell logcat -s JNI_ARRAY
--------- beginning of system
--------- beginning of main
12-24 06:42:40.546 3515 3515 I JNI_ARRAY: objectArray_in[0] : Hello
12-24 06:42:40.547 3515 3515 I JNI_ARRAY: objectArray_in[1] : JNI
12-24 06:42:40.548 3515 3515 E JNI_ARRAY: operateStringArray : Hello,
12-24 06:42:40.549 3515 3515 E JNI_ARRAY: operateStringArray : world!
12-24 06:42:40.549 3515 3515 E JNI_ARRAY: operateStringArray : JNI
12-24 06:42:40.549 3515 3515 E JNI_ARRAY: operateStringArray : is
12-24 06:42:40.549 3515 3515 E JNI_ARRAY: operateStringArray : fun
案例分析:
在本例中,Java类JNIArrayManager 中定义了一个operateStringArrray的Native方法,参数类型是String[]并且返回值类型也是String[],对应JNI中的jobjectArray类型。下面让我们对本地代码以一一分析。
解析String引用类型数组数据:
在该部分代码中我们将会解析通过JNI传递下来的Java的String引用类型数组内容,并打印出来。
(1) 首先通过GetArrayLength函数获取String类型数组的长度。
(2) 通过for循环调用GetObjectArrayElement获取String引用类型数组的元素String的值。
(3) 调用jstringToNative函数将jstring转换为char *并将其打印出来。
创建String引用类型数组数据并返回:
(1) 首先通过FindClass加载本类,其中的参数是类的描述符,我们这里的是String类的描述符是java/lang/String。
(2) 定义引用类型变量jobjectArray objectArray_out,然后通过NewObjectArray创建引用类型数组,关于该函数的具体用法参见章节1.2 NewObjectArray 。
(3) 开启for循环然后调用nativeTojstring将char *转换成jstring数据,接着调用SetObjectArrayElement对String类型引用数组的元素进行赋值,关于该函数的调用请参见前面章节。
(4) 返回objectArray_out给Java层。
如上就是访问和返回String类型一维数组的全过程,这个代码流程也可以扩展多其它引用类型数组的处理。在下面的2.3章节将会带领读者来处理自定义class数组的处理。
2.3 访问和返回自定义Class引用类型一维数组
在前面的章节里面我们以String引用类型数组为例讲解,在本章节中我们以自定义Class类型引用数组为例,讲解JNI中怎么访问和返回自定义Class引用类型一维数组。
Java端代码:
自定义Class类文件Person.java代码
package com.xxx.jni;
public class Person {
public int mAge;
public String mName;
@Override
public String toString() {
return "Person [age=" + mAge + ", name=" + mName + "]";
}
public int getAge() {
return mAge;
}
public void setAge(int age) {
this.mAge = age;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
}
Java端Native方法定义文件JNIArrayManager.java代码
package com.xxx.jni;
public class JNIArrayManager {
public native Person[] operateObjectArray(Person[] person);//自定义对象数组入参和返回值
static {
System.loadLibrary("jniArray");
}
}
private void operateObjectArray() {
JNIArrayManager jniArrayManager = new JNIArrayManager();
ArrayList<Person> persons = new ArrayList<Person>();
Person person_0 = new Person();
person_0.setAge(10);
person_0.setName("Hello");
Person person_1 = new Person();
person_1.setAge(110);
person_1.setName("JNI");
persons.add(person_0);
persons.add(person_1);
Person[] person_Array = new Person[2];
persons.toArray(person_Array);
Person[] persons_out = jniArrayManager.operateObjectArray(person_Array);
if (persons_out != null) {
for (int i = 0; i < persons_out.length; i++) {
Log.e("JNI_ARRAY ",
"operateObjectArray : " + persons_out[i].toString());
}
}
}
JNI端代码:
com_xxx_jni_JNIArrayManager.h文件代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_jni_JNIArrayManager */
#ifndef _Included_com_xxx_jni_JNIArrayManager
#define _Included_com_xxx_jni_JNIArrayManager
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xxx_jni_JNIArrayManager
* Method: operateObjectArray
* Signature: ([Lcom/xxx/jni/Person;)[Lcom/xxx/jni/Person;
*/
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateObjectArray
(JNIEnv *, jobject, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
com_xxx_jni_JNIArrayManager.cpp实现代码如下:
#include "com_xxx_jni_JNIArrayManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#define TAG "JNI_ARRAY"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
{
(env)->ExceptionDescribe();
(env)->ExceptionClear();
//printf("jstringToNative函数转换时,传入的参数str为空");
return NULL;
}
jbyteArray bytes = 0;
jthrowable exc;
char *result = 0;
if ((env)->EnsureLocalCapacity(2) < 0)
{
return 0; /* out of memory error */
}
jclass jcls_str = (env)->FindClass("java/lang/String");
jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B");
bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes);
exc = (env)->ExceptionOccurred();
if (!exc)
{
jint len = (env)->GetArrayLength( bytes);
result = (char *)malloc(len + 1);
if (result == 0)
{
//JNU_ThrowByName( "java/lang/OutOfMemoryError", 0);
(env)->DeleteLocalRef(bytes);
return 0;
}
(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result);
result[len] = 0; /* NULL-terminate */
}
else
{
(env)->DeleteLocalRef( exc);
}
(env)->DeleteLocalRef( bytes);
return (char*)result;
}
//将char * 转换成 jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{
//定义java String类 strClass
jclass strClass = (env)->FindClass("java/lang/String");
//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
//将char* 转换为byte数组
(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
//设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF( "utf-8");
//将byte数组转换为java String,并输出
return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding);
}
//和Java层的Person类相对应
typedef struct Person
{
char * mName;
int mAge;
};
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateObjectArray
(JNIEnv * env, jobject object, jobjectArray object_in)
{
/********解析自定义对象数组作为参数入参********/
//获取对应Java类Person的class以及各个属性
jclass clazz = env->FindClass("com/xxx/jni/Person");
//获取mAge变量对应的域信息
jfieldID mAge = env->GetFieldID(clazz, "mAge", "I");
//获取mName变量对应的域信息
jfieldID mName = env->GetFieldID(clazz, "mName","Ljava/lang/String;");
const int object_len = env->GetArrayLength(object_in);//获取数组长度
//将数组拆分转换成结构体
jobject mObject_in;
Person person_in[object_len];
for(int i = 0; i < object_len; i++)
{
mObject_in = env->GetObjectArrayElement(object_in, i);
person_in[i].mAge = env->GetIntField(mObject_in, mAge);
person_in[i].mName = jstringToNative(env, (jstring)env->GetObjectField(mObject_in, mName));
LOGE(TAG,"mObject_in[%d].mAge : %d\n", i, person_in[i].mAge);
LOGE(TAG,"mObject_in[%d].mName : %s\n", i, person_in[i].mName);
}
/********创建自定义Class数组对象并返回********/
const int object_num = 2;
//在JNI层创建数据
Person persons[object_num];
for(int i = 0 ; i < object_num; i++)
{
persons[i].mAge = 10 + i;
persons[i].mName = (char *)malloc(sizeof(char) * (20));//长度为20
sprintf(persons[i].mName,"name : %d", i);
}
// 这里获得构造方法
jmethodID initMethodId = env->GetMethodID(clazz, "<init>", "()V");
//将数据封装成对象然后再封装成数组
jobject mObject_out;
//创建Person对象数组
jobjectArray mArray_out = env->NewObjectArray(object_num, clazz, NULL);
for(int i = 0; i < object_num; i++)
{
mObject_out = env->NewObject(clazz, initMethodId);
env->SetIntField(mObject_out, mAge, persons[i].mAge);
env->SetObjectField(mObject_out, mName, nativeTojstring(env, persons[i].mName));
//将对象插入到数组的特定位置
env->SetObjectArrayElement(mArray_out, i, mObject_out);
}
return mArray_out;
}
运行演示:
λ adb lshell logcat -s JNI_ARRAY
--------- beginning of system
--------- beginning of main
12-24 09:04:09.968 4118 4118 I JNI_ARRAY: mObject_in[0].mAge : 10
12-24 09:04:09.968 4118 4118 I JNI_ARRAY: mObject_in[0].mName : Hello
12-24 09:04:09.968 4118 4118 I JNI_ARRAY: mObject_in[1].mAge : 110
12-24 09:04:09.968 4118 4118 I JNI_ARRAY: mObject_in[1].mName : JNI
12-24 09:04:09.969 4118 4118 E JNI_ARRAY: operateObjectArray : Person [age=10, name=name : 0]
12-24 09:04:09.970 4118 4118 E JNI_ARRAY: operateObjectArray : Person [age=11, name=name : 1]
案例分析:
在本例中,Java类JNIArrayManager 中定义了一个operateObjectArray的Native方法,参数类型是自定义Class类的Person[]数据并且返回值也是自定义Class类的Person[]数据。自定义的类也属于引用数组,对应JNI中的jobjectArray类型。下面让我们对本地代码以一一分析。
解析Person引用类型数组数据:
(1) 首先通过FindClass和GetFieldID获取获取对应Java类Person的class以及各个属性,关于这个的操作会在后续章节详细介绍。
(2) 调用GetArrayLength函数获取数组的长度。
(3) 开启for循环,然后在循环中调用GetObjectArrayElement获取自定义Class类型数组的元素值,接着调用GetIntField获取自定义Class类的变量值然后打印出来。关于怎么获取Java实例变量的我们会在后续章节专门讨论。
创建Person引用类型数组数据并返回:
(1) 在JNI层创建Java的Person类对应的C/C++的结构体数组Person persons[object_num]并赋值。
(2) 调用GetMethodID获取Java自定义类Person的构造函数域方法。
(3) 调用NewObjectArray函数创建Person引用数组对象。
(4) 开启循环,调用NewObject创建Person引用对象,并接着通过域进行赋值,最后通过SetObjectArrayElement将创建的Person引用对象放入对象数组中。
(5) 经过前面的一些操作此时Java自定Class引用型数组对象已经创建,然后直接返回会Java层。
通过上面的实例,我们讲解了怎么处理自定义Class引用对象数组,这里的Person类读者朋友们也可以扩展到其它的自定义Class,然后参照这个模式进行相关的解析。
2.3 访问和返回int类型二维数组
在前面的章节我们都是操作的一维数组,那么在这个章节让我们加大点难度,搞点大的直接操作二维数组。各位读者请抓好扶手坐稳,要开车了。
Java端代码
Java端Native方法定义文件JNIArrayManager.java代码
package com.xxx.jni;
public class JNIArrayManager {
public native Person[] operateObjectArray(Person[] person);//自定义对象数组入参和返回值
static {
System.loadLibrary("jniArray");
}
}
Java端调用代码
private void operateTwoIntDimArray(){
int [][] array_in = {{10, 100},{20, 200}};
JNIArrayManager jniArrayManager = new JNIArrayManager();
int [][] array_out = jniArrayManager.operateTwoIntDimArray(array_in);
if(array_out != null){
for(int i =0; i < array_out.length; i++){
for(int j = 0; j < array_out[i].length; j++){
Log.e("JNI_ARRAY", "operateTwoIntDimArray[" + i + "][" + j + "]" + array_out[i][j]);
}
}
}
}
JNI端代码
com_xxx_jni_JNIArrayManager.h文件代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_jni_JNIArrayManager */
#ifndef _Included_com_xxx_jni_JNIArrayManager
#define _Included_com_xxx_jni_JNIArrayManager
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xxx_jni_JNIArrayManager
* Method: operateTwoIntDimArray
* Signature: ([[I)[[I
*/
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateTwoIntDimArray
(JNIEnv *, jobject, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
com_xxx_jni_JNIArrayManager.cpp实现代码如下:
#include "com_xxx_jni_JNIArrayManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#define TAG "JNI_ARRAY"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateTwoIntDimArray
(JNIEnv * env, jobject object, jobjectArray objectArray_in)
{
/********** 解析从Java得到的int型二维数组 **********/
int i, j ;
const int row = env->GetArrayLength(objectArray_in);//获取二维数组的行数
jarray array = (jarray)env->GetObjectArrayElement(objectArray_in, 0);
const int col = env->GetArrayLength(array);//获取二维数组每行的列数
//根据行数和列数创建int型二维数组
jint intDimArrayIn[row][col];
for(i =0; i < row; i++)
{
array = (jintArray)env->GetObjectArrayElement(objectArray_in, i);
//操作方式一,这种方法会申请natvie memory内存
jint *coldata = env->GetIntArrayElements((jintArray)array, NULL );
for (j=0; j<col; j++) {
intDimArrayIn [i] [j] = coldata[j]; //取出JAVA类中int二维数组的数据,并赋值给JNI中的数组
}
//操作方式二,赋值,这种方法不会申请内存
env->GetIntArrayRegion((jintArray)array, 0, col, (jint*)&intDimArrayIn[i]);
env->ReleaseIntArrayElements((jintArray)array, coldata,0 );
}
for(int i = 0; i < row; i ++)
for(int j = 0; j < col; j++)
LOGE(TAG,"The intDimArrayIn[%d][%d] is %d\n", i, j, intDimArrayIn[i][j]);
/**************创建一个int型二维数组返回给Java**************/
const int row_out = 2;//行数
const int col_out = 2;//列数
//获取数组的class
jclass clazz = env->FindClass("[I");//一维数组的类
//新建object数组,里面是int[]
jobjectArray intDimArrayOut = env->NewObjectArray(row_out, clazz, NULL);
int tmp_array[row_out][col_out] = {{0,1},{2,3}};
for(i = 0; i< row_out; i ++)
{
jintArray intArray = env->NewIntArray(col_out);
env->SetIntArrayRegion(intArray, 0, col_out, (jint*)&tmp_array[i]);
env->SetObjectArrayElement(intDimArrayOut, i, intArray);
}
return intDimArrayOut;
}
运行演示:
λ adb logcat -s JNI_ARRAY
--------- beginning of main
--------- beginning of system
12-25 10:20:48.432 8281 8281 I JNI_ARRAY: The intDimArrayIn[0][0] is 10
12-25 10:20:48.432 8281 8281 I JNI_ARRAY: The intDimArrayIn[0][1] is 100
12-25 10:20:48.432 8281 8281 I JNI_ARRAY: The intDimArrayIn[1][0] is 20
12-25 10:20:48.432 8281 8281 I JNI_ARRAY: The intDimArrayIn[1][1] is 200
12-25 10:20:48.433 8281 8281 E JNI_ARRAY: operateTwoIntDimArray[0][0]0
12-25 10:20:48.433 8281 8281 E JNI_ARRAY: operateTwoIntDimArray[0][1]1
12-25 10:20:48.433 8281 8281 E JNI_ARRAY: operateTwoIntDimArray[1][0]2
12-25 10:20:48.433 8281 8281 E JNI_ARRAY: operateTwoIntDimArray[1][1]3
案例分析:
在本例中,Java类JNIArrayManager 中定义了一个operateTwoIntDimArray的Native方法,参数类型是int[][]二维数组数据并且返回值也是int[][]x型数组数据,对应JNI中的jobjectArray类型。下面让我们对本地代码以一一分析。
解析init[][]型类型数组数据:
这里主要的逻辑是逐步划分,先将二维数组解析为一维数组,然后再对一维数组进行解析。让我们就这代码一一分析!
(1) 首先调用GetArrayLength获取二维数组jobjectArray的行数。
(2) 然后调用GetObjectArrayElement获取二维数组jobjectArray的指定行数的数据。
(3) 调用GetArrayLength获取二维数组jobjectArray每一行的列数。
(4) 根据前面解析得到的二维数组jobjectArray的行数和列数,在C/C++层创建一个int[][]型二维数组intDimArrayIn。
(5) 开启循环,对每行二维数组每行逐步分解,调用GetObjectArrayElement获取每行的数据此时也是一个数组,然后继续调用GetIntArrayElements或者GetIntArrayRegion将数据保存在intDimArrayIn里面。
创建init[][]型类型数组数据并返回:
这里的步骤刚好和解析二维数组相反,先创建二维数组,然后创建二维数组行数数目的一维数组并且一维数组的长度为二维数组每行列数的长度。下面我们就这代码一一分析!
(1) 调用FindClass("[I")找到一维数组的类。
(2) 接着调用NewObjectArray创建一个一维数组,即二维数组也可以认为是一维数组,不过此时的一维数组的元素是一维数组。
(3) 开启循环,调用NewIntArray创建int型一维数组,接着调用SetIntArrayRegion对前面创建的一维数组赋值,最后调用SetObjectArrayElement将前面创建的一维数组放到二维数组里面去。
(4) 返回回init[][]二维数组intDimArrayOut到Java层。
好了到这里就解析完成了。
写在最后
各位读者看官朋友们,关于JNI对数组的处理就讲到这里了。本篇几乎将JNI中各种数组情况都有概括到了,只要仔细阅读本章,应该以后没有JNI数组问题能难住各位了。在最后麻烦读者朋友们如果本篇对你有帮助,关注和点赞一下,让然如果有错误和不足的地方也可以拍砖。青山不改绿水长流,各位江湖见!