热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android中的JNI数组操作教程

这篇文章主要给大家介绍了关于Android中JNI数组操作的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

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 GetIntArrayRegion(intArray_, 0, num, buf);
 sum = 0;
 for (int i = 0; i ReleaseIntArrayElements(intArray_, intArray, 0);
 return sum;
}

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

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

对应的 C++ 代码如下:

/**
 * 从 Native 返回 int 数组,主要调用 setArrayRegion 来填充数据,其他数据类型类似操作
 */
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 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 NewIntArray(size);
 if (iarr == NULL) {
  return NULL;
 }
 for (int j = 0; j 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 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 (本地下载)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • Vue 2 中解决页面刷新和按钮跳转导致导航栏样式失效的问题
    本文介绍了如何通过配置路由的 meta 字段,确保 Vue 2 项目中的导航栏在页面刷新或内部按钮跳转时,始终保持正确的 active 样式。具体实现方法包括设置路由的 meta 属性,并在 HTML 模板中动态绑定类名。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 本文基于对相关论文和开源代码的研究,详细介绍了LOAM(激光雷达里程计与建图)的工作原理,并对其关键技术进行了分析。 ... [详细]
  • 资源推荐 | TensorFlow官方中文教程助力英语非母语者学习
    来源:机器之心。本文详细介绍了TensorFlow官方提供的中文版教程和指南,帮助开发者更好地理解和应用这一强大的开源机器学习平台。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 构建基于BERT的中文NL2SQL模型:一个简明的基准
    本文探讨了将自然语言转换为SQL语句(NL2SQL)的任务,这是人工智能领域中一项非常实用的研究方向。文章介绍了笔者在公司举办的首届中文NL2SQL挑战赛中的实践,该比赛提供了金融和通用领域的表格数据,并标注了对应的自然语言与SQL语句对,旨在训练准确的NL2SQL模型。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文介绍如何使用 Sortable.js 库实现元素的拖拽和位置交换功能。Sortable.js 是一个轻量级、无依赖的 JavaScript 库,支持拖拽排序、动画效果和多种插件扩展。通过简单的配置和事件处理,可以轻松实现复杂的功能。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 本文总结了在使用Ionic 5进行Android平台APK打包时遇到的问题,特别是针对QRScanner插件的改造。通过详细分析和提供具体的解决方法,帮助开发者顺利打包并优化应用性能。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
author-avatar
不变de诺言2502890365
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有