Android C++ 系列:JNI 数组操作

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

image.png


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); } }


目录
相关文章
|
搜索推荐 编译器 C语言
【C++核心】特殊的元素集合-数组与字符串详解
这篇文章详细讲解了C++中数组和字符串的基本概念、操作和应用,包括一维数组、二维数组的定义和使用,以及C风格字符串和C++字符串类的对比。
535 5
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
1501 1
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
479 5
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
759 1
C++(十一)对象数组
本文介绍了C++中对象数组的使用方法及其注意事项。通过示例展示了如何定义和初始化对象数组,并解释了栈对象数组与堆对象数组在初始化时的区别。重点强调了构造器设计时应考虑无参构造器的重要性,以及在需要进一步初始化的情况下采用二段式初始化策略的应用场景。
|
算法 C++
c++学习笔记04 数组
这篇文章是C++学习笔记4,主题是数组。
247 4
|
C++ 索引 运维
开发与运维数组问题之在C++中数组名和指针是等价如何解决
开发与运维数组问题之在C++中数组名和指针是等价如何解决
173 6
|
存储 安全 C++
开发与运维数组问题之声明一个数组如何解决
开发与运维数组问题之声明一个数组如何解决
190 6
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
713 4
|
存储 C++ 容器
开发与运维数组问题之C++标准库中提供数据容器作为数组的替代如何解决
开发与运维数组问题之C++标准库中提供数据容器作为数组的替代如何解决
241 5