JNI 中有两种数组操作,基础数据类型数组和对象数组,JNI 对待基础数据类型数组和对象数组是不一样的。

基本数据类型数组

对于基本数据类型数组,JNI 都有和 Java 相对应的结构,在使用起来和基本数据类型的使用类似。

在 Android JNI 基础知识篇提到了 Java 数组类型对应的 JNI 数组类型。比如,Java int 数组对应了 jintArray,boolean 数组对应了 jbooleanArray。

如同 String 的操作一样,JNI 提供了对应的转换函数:GetArrayElements、ReleaseArrayElements。

    intArray = env->GetIntArrayElements(intArray_, NULL);
env->ReleaseIntArrayElements(intArray_, intArray, 0);

另外,JNI 还提供了如下的函数:

  • GetTypeArrayRegion / SetTypeArrayRegion

将数组内容复制到 C 缓冲区内,或将缓冲区内的内容复制到数组上。

  • GetArrayLength

得到数组中的元素个数,也就是长度。

  • NewTypeArray

返回一个指定数据类型的数组,并且通过 SetTypeArrayRegion 来给指定类型数组赋值。

  • GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical

如同 String 中的操作一样,返回一个指定基础数据类型数组的直接指针,在这两个操作之间不能做任何阻塞的操作。

实际操作如下:

    // Java 传递 数组 到 Native 进行数组求和
private native int intArraySum(int[] intArray, int size);

对应的 C++ 代码如下:

JNIEXPORT jint JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_intArraySum(JNIEnv *env, jobject instance,
jintArray intArray_, jint num) {
jint *intArray;
int sum = 0;
// 操作方法一:
// 如同 getUTFString 一样,会申请 native 内存
intArray = env->GetIntArrayElements(intArray_, NULL);
if (intArray == NULL) {
return 0;
}
// 得到数组的长度
int length = env->GetArrayLength(intArray_);
LOGD("array length is %d", length);
for (int i = 0; i < length; ++i) {
sum += intArray[i];
}
LOGD("sum is %d", sum); // 操作方法二:
jint buf[num];
// 通过 GetIntArrayRegion 方法来获取数组内容
env->GetIntArrayRegion(intArray_, 0, num, buf);
sum = 0;
for (int i = 0; i < num; ++i) {
sum += buf[i];
}
LOGD("sum is %d", sum);
// 使用完了别忘了释放内存
env->ReleaseIntArrayElements(intArray_, intArray, 0);
return sum;
}

假如需要从 JNI 中返回一个基础数据类型的数组,对应的代码如下:

    // 从 Native 返回基本数据类型数组
private native int[] getIntArray(int num);

对应的 C++ 代码如下:

/**
* 从 Native 返回 int 数组,主要调用 set<Type>ArrayRegion 来填充数据,其他数据类型类似操作
*/
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getIntArray(JNIEnv *env, jobject instance,
jint num) {
jintArray intArray;
intArray = env->NewIntArray(num); jint buf[num];
for (int i = 0; i < num; ++i) {
buf[i] = i * 2;
} // 使用 setIntArrayRegion 来赋值
env->SetIntArrayRegion(intArray, 0, num, buf);
return intArray;
}

以上例子,基本把相关的操作都使用上了,可以发现和 String 的操作大都是相似的。

对象数组

对于对象数组,也就是引用类型数组,数组中的每个类型都是引用类型,JNI 只提供了如下函数来操作。

  • GetObjectArrayElement / SetObjectArrayElement

和基本数据类型不同的是,不能一次得到数据中的所有对象元素或者一次复制多个对象元素到缓冲区。只能通过上面的函数来访问或者修改指定位置的元素内容。

字符串和数组都是引用类型,因此也只能通过上面的方法来访问。

例如在 JNI 中创建一个二维的整型数组并返回:

    // 从 Native 返回二维整型数组,相当于是一个一维整型数组,数组中的每一项内容又是数组
private native int[][] getTwoDimensionalArray(int size);

二维数组具有特殊性在于,可以将它看成一维数组,其中数组的每项内容又是一维数组。

具体 C++ 代码如下:

/**
* 从 Native 返回一个二维的整型数组
*/
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getTwoDimensionalArray(JNIEnv *env,
jobject instance,
jint size) {
// 声明一个对象数组
jobjectArray result;
// 找到对象数组中具体的对象类型,[I 指的就是数组类型
jclass intArrayCls = env->FindClass("[I"); if (intArrayCls == NULL) {
return NULL;
}
// 相当于初始化一个对象数组,用指定的对象类型
result = env->NewObjectArray(size, intArrayCls, NULL); if (result == NULL) {
return NULL;
}
for (int i = 0; i < size; ++i) {
// 用来给整型数组填充数据的缓冲区
jint tmp[256];
// 声明一个整型数组
jintArray iarr = env->NewIntArray(size);
if (iarr == NULL) {
return NULL;
}
for (int j = 0; j < size; ++j) {
tmp[j] = i + j;
}
// 给整型数组填充数据
env->SetIntArrayRegion(iarr, 0, size, tmp);
// 给对象数组指定位置填充数据,这个数据就是一个一维整型数组
env->SetObjectArrayElement(result, i, iarr);
// 释放局部引用
env->DeleteLocalRef(iarr);
}
return result;
}

首先需要使用 NewObjectArray 方法来创建对象数组。

然后使用 SetObjectArrayElement 函数填充数据时,需要构建好每个位置对应的对象。这里就使用了 NewIntArray 来创造了一个对象,并给对象填充数据后,在赋值给对象数组。

通过一个 for 循环就完成给对象数组赋值的操作。

在创建对象数组时,有一个操作是找到对应的对象类型,通过 findClass 方法。findClass 的参数 [I 这里就涉及到 Java 与 JNI 对应签名的转换。

Java 与 JNI 签名的转换

在前一篇文章中,用表格列出了 Java 与 JNI 对应的数据类型格式的转换关系,现在要列举的是 Java 与 JNI 对应签名的转换关系。

这里的签名指的是在 JNI 中去查找 Java 中对应的数据类型、对应的方法时,需要将 Java 中的签名转换成 JNI 所能识别的。

对于类的签名转换

对于 Java 中类或者接口的转换,需要用到 Java 中类或者接口的全限定名,把 Java 中描述类或者接口的 . 换成 / 就好了,比如 String 类型对应的 JNI 描述为:

java/lang/String     // . 换成 /

对于数组类型,则是用 [ 来表示数组,然后跟一个字段的签名转换。

[I      // 代表一维整型数组,I 表示整型
[[I // 代表二维整型数组
[Ljava/lang/String; // 代表一维字符串数组,
对于字段的签名转换

对应基础类型字段的转换:

Java 类型 JNI 对应的描述转
boolean Z
byte B
char C
short S
int I
long J
float F
double D

对于引用类型的字段签名转换,是大写字母 L 开头,然后是类的签名转换,最后以 ; 结尾。

Java 类型 JNI 对应的描述转换
String Ljava/lang/String;
Class Ljava/lang/Class;
Throwable Ljava/lang/Throwable
int[] "[I"
Object[] "[Ljava/lang/Object;"
对于方法的签名转换

对于方法签名描述的转换,首先是将方法内所有参数转换成对应的字段描述,并全部写在小括号内,然后在小括号外再紧跟方法的返回值类型描述。

Java 类型 JNI 对应的描述转换
String f(); ()Ljava/lang/String;
long f(int i, Class c); (ILjava/lang/Class;)J
String(byte[] bytes); ([B)V

这里要注意的是在 JNI 对应的描述转换中不要出现空格。

了解并掌握这些转换后,就可以进行更多的操作了,实现 Java 与 C++ 的相互调用。

比如,有一个自定义的 Java 类,然后再 Native 中打印类的对象数组的某一个字段值。

    private native void printAnimalsName(Animal[] animal);

具体 C++ 代码如下:

/**
* 打印对象数组中的信息
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_printAnimalsName(JNIEnv *env, jobject instance,
jobjectArray animals) {
jobject animal;
// 数组长度
int size = env->GetArrayLength(animals);
// 数组中对应的类
jclass cls = env->FindClass("com/glumes/cppso/model/Animal");
// 类对应的字段描述
jfieldID fid = env->GetFieldID(cls, "name", "Ljava/lang/String;");
// 类的字段具体的值
jstring jstr;
// 类字段具体值转换成 C/C++ 字符串
const char *str; for (int i = 0; i < size; ++i) {
// 得到数组中的每一个元素
animal = env->GetObjectArrayElement(animals, i);
// 每一个元素具体字段的值
jstr = (jstring) (env->GetObjectField(animal, fid));
str = env->GetStringUTFChars(jstr, NULL);
if (str == NULL) {
continue;
}
LOGD("str is %s", str);
env->ReleaseStringUTFChars(jstr, str);
}
}

具体示例代码可参考我的 Github 项目,欢迎 Star。

https://github.com/glumes/AndroidDevWithCpp

Android JNI 数组操作的更多相关文章

  1. JNI数组操作

    在Java中数组分为两种: 1.基本类型数组 2.对象类型(Object[])的数组(数组中存放的是指向Java对象中的引用) 一个能通用于两种不同类型数组的函数: GetArrayLength(ja ...

  2. Android jni 编程4(对基本类型二维整型数组的操作)

    Android jni 编程 对于整型二维数组操作: 类型一:传入二维整型数组,返回一个整型值 类型二:传入二维整型数组,返回一个二维整型数组 声明方法: private native int Sum ...

  3. android JNI 一维数组、二维数组的访问与使用

    在JNI中访问JAVA类中的整型.浮点型.字符型的数据比较简单,举一个简单的例子,如下: //得到类名 jclass cls = (*env)->GetObjectClass(env, obj) ...

  4. Android jni 二维数组 传递

    学习Android Jni时,一个二维 整数 数组的传递花了我好长时间,在网上查的资料都不全,当然最后是成功了,写在这里是为了自己记住,当然有人搜索到并利用了我会很高兴.   in Android J ...

  5. [转载]—— Android JNI知识点

    Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 ...

  6. 一天掌握Android JNI本地编程 快速入门

    一.JNI(Java Native Interface)        1.什么是JNI:               JNI(Java Native Interface):java本地开发接口   ...

  7. 【转】android JNI

    原文网址:http://jinguo.iteye.com/blog/696185 Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码 ...

  8. Android JNI开发提高篇

    有关JNI的开发技术,我们继续围绕Android平台进行,JNI可以支持C或C++,从目前为止我们写过的JNI代码均为C实现的,即文件名为.C而C++的和这些有什么不同呢? Android平台上的JN ...

  9. Android JNI(NDK)开发总结

    早就知道Java有个jni可以调用本地化代码,一直没有动力去研究它,现在公司想通过在Android中调用本地化代码来申请较多的内存以突破Android对单个进程的内存限制,这确实是可行的:我的Nexu ...

随机推荐

  1. Caused by: java.sql.SQLException: Field 'category_id' doesn't have a default value

    ### The error may involve com.qingmu.core.dao.ad.ContentDao.insertSelective-Inline ### The error occ ...

  2. python的反射函数(hasattr()、getattr()、setattr()与delattr())和类的内置属性attr(__getattr()__、__setattr()__与__delattr()__)

    主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省),有四个可以实现自省函数. hasattr(object,name) 判断object中是否有name字符串对应的属性或方法,返回Tr ...

  3. python之协程函数、递归、二分法

    一.协程函数: 协程函数的语法: def eater(name): print('%s说:我开动啦' %name) food_list=[] while True: food=yield food_l ...

  4. ACM札记

    1. 逗号表达式 在“计蒜客“的ACM教程中,看到这样一段很好的代码: int n; while (scanf("%d", &n), n) { //do something ...

  5. python框架相关问题

    面试其他篇 目录: 1.1

  6. VSCode 预览 .md 文件

    VSCode安装md插件 选择Extensions, 输入Markdown Theme Kit,下面会出现相应插件,点击install(截图中因为我已经安装,所以是设置) 或者安装插件Markdown ...

  7. Python3基础 dict 推导式 生成10以内+奇数的值为True 偶数为False的字典

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  8. P2761 软件补丁问题

    P2761 软件补丁问题 思路 貌似不用网络流,直接状态压缩 用spfa跑最短路,直接判断是否能过 位运算太渣了,WA了好几发 代码 #include <bits/stdc++.h> us ...

  9. FJUT 倒水(倒水问题)题解

    题意:开学了, fold拿着两个无刻度, 容量分别是5L和7L的量筒来问Anxdada, 说水是无限的, 并且可以无限次将杯子装满或者清空, 那怎么用这个两个量筒倒出恰好4L水了? 我说简单啊, 先装 ...

  10. sudo用法记录

    使用root用户,visudo命令(实际是编辑/etc/sudoers文件),用法和vim一样,末行模式:wq退出,如还有提示,使用大写"Q"保存退出,小写"e" ...