Android JNI与Java类的转换调用

1. 前言

一般在Android中,对于JNI的调用,基本的数据类型就能满足要求了,具体可以看我的这一篇文章 : Android JNI/NDK 入门从一到二,但是最近在项目中遇到了基本类型满足不了需要的情况,需要在JNI中创建并操作Java类,最后再返回到Java层

具体需求是这样的 : 需要同时返回坐标点和字符串,并且一次性返回的还不止一组,而是有N组。
这么返回值就需要用Map<String,Array<Int>>,或者使用Array<Java对象>,那么具体需要怎么做呢 ? 我们下文一步一步来实现。

2. 类型签名

首先我们需要知道JNI中各种类型的签名(可以理解为简写)是什么。
JNI中提供了多种类型签名来表示Java中的各种基本数据类型以及引用类型。
这些类型签名在JNI中用于查找方法ID字段ID以及其他与Java类型交互的操作。
以下是主要的类型签名列表:

2.1 基本数据类型:

  • Z:boolean
  • B:byte
  • C:char
  • S:short
  • I:int
  • J:long
  • F:float
  • D:double

2.2 引用类型:

  • L<全限定类名>; :对象或类实例。例如,对于java.lang.String类,其签名是Ljava/lang/String;
  • [:数组标记,后面跟数组元素的类型签名。如[I表示int数组,[Ljava/lang/String;表示String数组。
    方法描述符:

2.3 返回值

方法没有返回值时,用V表示void
方法有返回值时,使用上述基本类型或引用类型的签名。
方法参数和返回值: 方法签名的格式是(参数1类型签名 参数2类型签名 ... 参数n类型签名) 返回值类型签名,例如:

(II)V  // 表示一个接受两个int参数且无返回值的方法
(Ljava/lang/String;)[Ljava/lang/String;  // 表示一个接受一个String参数并返回String数组的方法

3. 涉及的一些API

3.1 FindClass

于在Java类加载器中查找指定的类,返回类型为jclass

jclass cls = env->FindClass("java类的包名/类名")

3.2 GetMethodID

获取Java类的指定方法的ID,返回类型为jmethodID

jmethodID methodId = env->GetMethodID()

3.3 NewObject

创建一个新的Java对象实例

jobject obj = env->NewObject()

3.4 CallObjectMethod

用于调用Java对象的指定方法

jobject result = env->CallObjectMethod()

需要注意的是,CallObjectMethod方法返回的结果类型是jobject,这意味着它返回的是Java对象的引用。如果方法返回的是原始类型(如int、float等),你需要使用相应的方法(如CallIntMethodCallFloatMethod等)来调用。
另外,如果方法抛出了异常,CallObjectMethod会返回一个空引用(nullptr)。因此,在调用该方法后,通常需要检查返回值是否为空,以确定方法是否成功执行。

3.5 GetFieldID

用于获取Java类的指定字段的ID

jfieldID fid = env->GetFieldID()

3.6 GetIntField

用于获取Java对象的指定整型字段的值,其他类型的值的获取也是类似的

jint value = env->GetIntField()

3.7 setIntFiled

用于设置Java对象的指定整型字段的值,其他类型的值的设置也是类似的

void SetIntField(jobject obj, jfieldID fieldID, jint value)

进行调用

env->SetIntField(obj, filedId, value);

4. 返回Map<String,Array<Int>>

首先来尝试下使用Map<String,Array<Int>>返回

4.1 实现Map<String, String>的返回

要实现Map<String,Array<Int>>,那么第一步就需要实现Map<String, String>
Map<String, String>的实现肯定比Map<String,Array<Int>>的实现简单。

4.1.1 定义JNI接口
external fun test1(): Map<String, String>
4.1.2 实现JNI接口
Java_com_heiko_myndktest_MainActivity_test1(JNIEnv *env, jobject thiz) {jclass mapClass = env->FindClass("java/util/HashMap");jmethodID initMethod = env->GetMethodID(mapClass, "<init>", "()V");jobject javaMap = env->NewObject(mapClass, initMethod);jmethodID putMethod = env->GetMethodID(mapClass, "put","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");jstring keyString = env->NewStringUTF("key1"); jstring valueString = env->NewStringUTF("value1");env->CallObjectMethod(javaMap, putMethod, keyString, valueString);return javaMap;
}
4.1.3 调用JNI方法
val map = test1()
for (entry in map.entries) {Log.i("ZZZZ", "key:${entry.key} value:${entry.value}")
}

打印结果

key:key1 value:value1

4.2 实现Map<String, Array<Int>>的返回

4.2.1 Array<Int>IntArray的区别

首先我们要明白Array<Int>IntArray的区别是什么。

java

public interface MyTest {int[] t1();int[][] t2();Map<String, String> t3();Map<String, Integer[]> t4();Integer[] t5();
}

对应的kotlin

interface MyTest {//int[] t1();fun t1(): IntArray?// int[][] t2();fun t2(): Array<IntArray?>?// Map<String, String> t3();fun t3(): Map<String?, String?>?// Map<String, Integer[]> t4();fun t4(): Map<String?, Array<Int?>?>?//Integer[] t5();fun t5(): Array<Int?>?
}

可以发现,kotlin中的Array<Int>对应java中的Integer[],而kotlin中的IntArray对应着java中的int[],这两者是有本质区别的 : int对应JNI中的jInt,而IntegerJNI中却是jobject

4.2.2 创建JNI
external fun test2(): Map<String, Array<Int>>
4.2.3 实现JNI
extern "C"
JNIEXPORT jobject JNICALL
Java_com_heiko_myndktest_MainActivity_test2(JNIEnv *env, jobject thiz) {jclass mapClass = env->FindClass("java/util/HashMap");jmethodID initMethod = env->GetMethodID(mapClass, "<init>", "()V");jobject javaMap = env->NewObject(mapClass, initMethod);jmethodID putMethod = env->GetMethodID(mapClass, "put","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");jstring keyString = env->NewStringUTF("myKey"); // 示例:创建一个字符串键// 创建Java Integer数组类对象jclass integerClass = env->FindClass("java/lang/Integer");jobjectArray javaArray = env->NewObjectArray(5, integerClass, nullptr);// 假设你有一个C++端的int数组或者其他结构存储数据jint nativeData[5] = {1, 2, 3, 4, 5}; // 这里填充实际的数据for (jint i = 0; i < 5; ++i) {// 将C++中的int值转换为Java的Integer对象jmethodID integerValueOfMethod = env->GetStaticMethodID(integerClass, "valueOf", "(I)Ljava/lang/Integer;");jobject javaInteger = env->CallStaticObjectMethod(integerClass, integerValueOfMethod, nativeData[i]);// 将Java Integer对象添加到数组中env->SetObjectArrayElement(javaArray, i, javaInteger);// 删除局部引用以避免内存泄漏(可选,在JNI调用结束时自动发生)env->DeleteLocalRef(javaInteger);}env->CallObjectMethod(javaMap, putMethod, keyString, javaArray);return javaMap;
}
4.2.4 调用JNI
val map2 = test2()
for (entry in map2.entries) {Log.i("ZZZZ", "key:${entry.key}")var str = ""for (i in entry.value) {str+=" $i"}Log.i("ZZZZ","value:$str")
}

打印日志如下

key:myKey
value: 1 2 3 4 5

5. 返回Java对象

5.1 定义Java类

public class JavaBean {public int myPublicInt = 1;public float myPublicFloat = 2.35F;public String myPublicString = "hello";public int[] myPublicIntArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};public float[] myPublicFloatArray = new float[]{1.2F, 2.3F, 3.4F,4.5F};public String[] myPublicStringArray = new String[]{"hello", "world", "!"};public JavaBean() {}private int intValue = 77;private float floatValue = 9.788F;private String stringValue = "world";public int getIntValue() {return intValue;}public void setIntValue(int intValue) {this.intValue = intValue;}public void setFloatValue(float value) {this.floatValue = value;}public float getFloatValue() {return floatValue;}public String getStringValue() {return stringValue;}public void setStringValue(String stringValue) {this.stringValue = stringValue;}
}

5.2 定义JNI

external fun test3(): JavaBean

5.3 实现JNI

jobject createJavaBean(JNIEnv *env, int index) {jclass clazz = env->FindClass("com/heiko/myndktest/JavaBean");/*if (cls_hello== nullptr){throw ""}*/jmethodID constructorId = env->GetMethodID(clazz, "<init>", "()V");// 调用构造方法创建新对象jobject newObj = env->NewObject(clazz, constructorId);//赋值public intjfieldID myIntFiledId = env->GetFieldID(clazz, "myPublicInt", "I");jint myPublicInt = env->GetIntField(newObj, myIntFiledId);LOGD("myPublicInt %d", myPublicInt);env->SetIntField(newObj, myIntFiledId, index);//赋值public floatjfieldID myFloatFiledId = env->GetFieldID(clazz, "myPublicFloat", "F");jfloat myPublicFloat = env->GetFloatField(newObj, myFloatFiledId);LOGD("myPublicFloat %f", myPublicFloat);jfloat floatValue1 = 5.67f;env->SetFloatField(newObj, myFloatFiledId, floatValue1);//赋值public stringjfieldID myStringFiledId = env->GetFieldID(clazz, "myPublicString", "Ljava/lang/String;");//jstring stringResult = (jstring)env->GetObjectField(newObj, myStringFiledId);jstring mPublicString = static_cast<jstring>(env->GetObjectField(newObj, myStringFiledId));const char *mPublicStringUTFChars = env->GetStringUTFChars(mPublicString, nullptr);LOGD("stringResult %s", mPublicStringUTFChars);//别忘了释放资源env->ReleaseStringUTFChars(mPublicString, mPublicStringUTFChars);const char *stringValue = "你好呀";jstring javaStringValue = env->NewStringUTF(stringValue);env->SetObjectField(newObj, myStringFiledId, javaStringValue);//获取float方法jmethodID getFloatValueMethodId = env->GetMethodID(clazz, "getFloatValue", "()F");jfloat javaFloatValue = env->CallFloatMethod(newObj, getFloatValueMethodId);LOGD("javaFloatValue:%f", javaFloatValue);//设置float方法jmethodID setFloatValueMethodId = env->GetMethodID(clazz, "setFloatValue", "(F)V");jfloat floatValue = 3.14f;env->CallVoidMethod(newObj, setFloatValueMethodId, floatValue);//获得string方法返回值jmethodID getStringValueMethodId = env->GetMethodID(clazz, "getStringValue","()Ljava/lang/String;");jstring stringMethodValue = (jstring) env->CallObjectMethod(newObj, getStringValueMethodId);const char *stringMethodValueChars = env->GetStringUTFChars(stringMethodValue, nullptr);LOGD("stringMethodValue %s", stringMethodValueChars);//别忘了释放资源env->ReleaseStringUTFChars(mPublicString, stringMethodValueChars);//设置sring方法jmethodID setStringValueMethodId = env->GetMethodID(clazz, "setStringValue","(Ljava/lang/String;)V");const char *sss = "我的天!";jstring ssss = env->NewStringUTF(sss);env->CallVoidMethod(newObj, setStringValueMethodId, ssss);//获取float[]jfieldID myPublicFloatArrayFiledId = env->GetFieldID(clazz, "myPublicFloatArray", "[F");jfloatArray myPublicFloatArray = (jfloatArray) env->GetObjectField(newObj,myPublicFloatArrayFiledId);// 获取数组长度jsize myPublicFloatArrayLen = env->GetArrayLength(myPublicFloatArray);// 获取jfloatArray的本地引用和元素指针jboolean *isCopy = nullptr;jfloat *elements = env->GetFloatArrayElements(myPublicFloatArray, isCopy);for (int i = 0; i < myPublicFloatArrayLen; ++i) {if (i == 1) {//修改某个索引值elements[i] = 9.91f;}LOGD("myPublicFloatArray[%d]:%f", i, elements[i]);}//设置float[]jfloatArray newFloatArray = env->NewFloatArray(5);jfloat *elements2 = env->GetFloatArrayElements(newFloatArray, nullptr);for (int i = 0; i < 5; ++i) {elements2[i] = 2.2f * i;}env->SetObjectField(newObj, myPublicFloatArrayFiledId, newFloatArray);//释放env->ReleaseFloatArrayElements(newFloatArray, elements2, 0);// 释放本地引用和元素指针env->ReleaseFloatArrayElements(myPublicFloatArray, elements, 0);return newObj;
}extern "C"
JNIEXPORT jobject JNICALL
Java_com_heiko_myndktest_MainActivity_test3(JNIEnv *env, jobject thiz) {jobject newObj = createJavaBean(env, 9);return newObj;
}

5.4 调用JNI

val result = test3()
Log.i("ZZZZ", "myPublicInt:${result.myPublicInt} myPublicFloat:${result.myPublicFloat} myPublicString:${result.myPublicString} floatValue:${result.floatValue} stringValue:${result.stringValue} floatArray:${Arrays.toString(result.myPublicFloatArray)}")

打印日志如下

myPublicInt:9 myPublicFloat:5.67 myPublicString:你好呀 floatValue:3.14 stringValue:我的天! floatArray:[0.0, 2.2, 4.4, 6.6000004, 8.8]

6. 返回Array<Java对象>

返回Array<Java对象>,只需要在外面再套一层jobjectArray就行了。

6.1 定义JNI

external fun test4(): Array<JavaBean>

6.2 实现JNI

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_heiko_myndktest_MainActivity_test4(JNIEnv *env, jobject thiz) {jclass clazz = env->FindClass("com/heiko/myndktest/JavaBean");jobjectArray objArray = env->NewObjectArray(5, clazz, NULL);for (int i = 0; i < 5; ++i) {jobject newObj = createJavaBean(env, i);env->SetObjectArrayElement(objArray, i, newObj);}return objArray;
}

6.3 调用JNI

val resultArray = test4()
Log.i("ZZZZ", "resultArray.length:" + resultArray.size)
for (javaBean in resultArray) {Log.i("ZZZZ","myPublicInt:" + javaBean.myPublicInt + " stringValue:" + javaBean.stringValue)
}

打印日志结果

resultArray.length:5
myPublicInt:0 stringValue:我的天!
myPublicInt:1 stringValue:我的天!
myPublicInt:2 stringValue:我的天!
myPublicInt:3 stringValue:我的天!
myPublicInt:4 stringValue:我的天!

7. 返回二维数组

7.1 定义JNI

external fun test5(): Array<IntArray>

7.2 实现JNI

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_heiko_myndktest_MainActivity_test5(JNIEnv *env, jobject thiz) {int rows = 5; // 行数int cols = 4; // 列数jclass intClass = env->FindClass("[I");jobjectArray result = env->NewObjectArray(rows, intClass, NULL);for (jint i = 0; i < rows; i++) {jintArray intArray = env->NewIntArray(cols);jint *elements = env->GetIntArrayElements(intArray, nullptr);if (elements == nullptr) {env->DeleteLocalRef(intArray);return nullptr;}elements[0] = i * 4 + 0;elements[1] = i * 4 + 1;elements[2] = i * 4 + 2;elements[3] = i * 4 + 3;env->ReleaseIntArrayElements(intArray, elements, 0);env->SetObjectArrayElement(result, i, intArray);}return result;
}

7.3 调用JNI

val array = test5()
Log.i("ZZZZ", "二维数组长度:" + array.size)
for (i in 0 until array.size) {val childSize = array[i].sizevar str = ""for (j in 0 until childSize) {str += " ${array[i][j]}"}Log.i("ZZZZ", "二维数组:${str}")
}

7.4 打印日志结果

二维数组长度:5
二维数组: 0 1 2 3
二维数组: 4 5 6 7
二维数组: 8 9 10 11
二维数组: 12 13 14 15
二维数组: 16 17 18 19

8. 其他

我的其他和JNI相关的文章 :

Android JNI/NDK 入门从一到二-CSDN博客
Android和JNI交互 : 常见的图像格式转换 : NV21、RGBA、Bitmap等_安卓代码图片格式转换-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/623212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rust-语句和表达式

if-else Rust中if-else表达式的作用是实现条件分支。if-else表达式的构成方式为&#xff1a;以if关键字开头&#xff0c;后面跟上条件表达式&#xff0c;后续是结果语句块&#xff0c;最后是可选的else块。条件表达式的类型必须是bool。 if-else结构还可以当表达式使用 loop …

如何用LLM和自有知识库搭建智能agent?

用LangChain建立知识库&#xff0c;文末中也推荐其他方案。 项目源码&#xff1a;ChatPDF实现 LangChain Indexes使用 对加载的内容进行索引&#xff0c;在indexes中提供了一些功能&#xff1a; Document Loaders&#xff0c;加载文档Text Splitters&#xff0c;文档切分V…

nautilus`文件管理器打开文件管理器

使用nautilus文件管理器打开文件管理器&#xff0c;并使用管理员权限。以下是使用命令行的步骤&#xff1a; 打开终端&#xff08;命令行界面&#xff09;&#xff0c;你可以通过按下Ctrl Alt T组合键来打开终端。 在终端中&#xff0c;使用以下命令以管理员权限打开文件管理…

开发实践5_后台管理^/ 分_页器

以下学习 朔宁夫 开发课 。&#xff08;Python&#xff09; 一 基本使用 创建超级用户 terminal // python manage.py createsuperuser 访问地址 //Log in | Django site adminhttp://127.0.0.1:8000/admin/login/?next/admin/ superuserr login django自带admin功能。其…

一文读懂「Prompt Engineering」提示词工程

在了解提示过程之前&#xff0c;先了解一下什么是提示prompt&#xff0c;见最后附录部分 一、什么是Prompt Engingering&#xff1f; 提示工程&#xff08;Prompt Engingering&#xff09;&#xff0c;也被称为上下文提示&#xff08;In-Context Prompting&#xff09;&#x…

Django项目中的默认文件都有什么用

manager.py&#xff1a; 是django用于管理本项目的命令行工具&#xff0c;之后进行站点运行&#xff0c;数据库自动生成等都是通过本文件完成。 djangoStudy/__init__.py&#xff1a; 告诉python该目录是一个python包&#xff0c;暂无内容&#xff0c;后期一些工具的初始化可…

【数据库】间隙锁Gap Lock

什么是间隙锁 间隙锁&#xff08;Gap Lock&#xff09;&#xff1a;间隙锁是&#xff08;RR级别下&#xff09;一个在索引记录之间的间隙上的锁&#xff0c;可以是两个索引记录之间&#xff0c;也可能是第一个索引记录之前或最后一个索引之后的空间。间隙锁&#xff08;Gap Lo…

Github登录时,要二次验证.Github 2FA登录问题

2023下半年, Github登录时,要二次验证. 点击“Enable 2FA now”跳到2FA验证界面。 点setup key是到secret串。 这里有一个可以不用app的验证方法。填入secret串,运行后,会得到验证码. https://blog.csdn.net/dejavu_980323/article/details/132318107 按照这个文章,得到验证…

记csv、parquet数据预览一个bug的解决

文章目录 一、概述二、实现过程1. 业务流程2. 业务逻辑3. 运行结果 三、bug现象1. 单元测试2.运行结果 三、解决思路1. 思路一2. 思路二 一、概述 工作中遇到通过sparksession解析csv、parquet文件并预览top100的需求。 二、实现过程 1. 业务流程 #mermaid-svg-Tzp1pJTvJunU…

227. Basic Calculator II

227. Basic Calculator II class Solution:def calculate(self, s: str) -> int:ititer(re.split(r([\\-\*\/]),s))lastansint(next(it))for op in it:nint(next(it))if op in -:if op-:n-nansnelse:if op*:nlast*nelse:nint(last/n)ansans-lastnlastnreturn ans 注意这个…

分布式任务调度平台XXL-JOB使用(二)

说明&#xff1a;之前总结过在CentOS系统里使用XXL-JOB。但在代码开发阶段&#xff0c;我们有时需要在本地环境测试定时任务代码&#xff0c;本文介绍如何在Windows系统使用XXL-JOB。 下载 &#xff08;1&#xff09;下载代码&#xff0c;解压打开 首先&#xff0c;去Github…

多线程实现方式二

这里写目录标题 RunnableRunable接口实现多线程使用Runnable接口实现多线程的好处线程同步 - 三个窗口买票lock实现同步锁 Runnable Runable接口实现多线程 实现 Runnable 接口 定义一个类 MyRunnable 实现 Runnable 接口 在 MyRunnable 类中重写 run() 方法 创建 Thread 类…

Python系列(3)—— 变量

变量 一、变量命名规范二、变量赋值三、变量的数据类型四、变量的作用域五、变量类型转换 Python编程中&#xff0c;变量是存储数据的容器。它们用于存储各种数据类型&#xff0c;如整数、浮点数、字符串、列表、字典等。理解变量及其工作原理是Python编程的基础。 一、变量命…

在Android原生项目中 创建 Flutter模块

前言 应用场景&#xff1a;在已有的Android原生项目中&#xff0c;引入Flutter模块&#xff0c;摸索了两天&#xff0c;终于给整出来了&#xff1b; 如果是新项目&#xff0c;最好直接创建Flutter项目&#xff0c;然后在Fluter的 android / ios目录中&#xff0c;写原生代码&…

深思熟虑可能性模型介绍与使用

深思熟虑可能性模型介绍与使用 如何联系我 作者&#xff1a;鲁伟林 邮箱&#xff1a;thinking_fioa163.com或vlinyes163.com 版权声明&#xff1a;文章和记录为个人所有&#xff0c;如果转载或个人学习&#xff0c;需注明出处&#xff0c;不得用于商业盈利行为。 背景 20…

力扣27-移除元素

移除元素 题目链接 解题思路 ans为左指针 循环遍历为右指针 右指针开始遍历整个列表&#xff0c;如果指针所指的元素与val值不等&#xff0c;那么这个元素不能被删除&#xff0c;将其放入到左指针索引处。 因为return的是列表的长度&#xff0c;ans恰好也是删除完元素后列表的…

[牛客周赛复盘] 牛客周赛 Round 28 20240114

[牛客周赛复盘] 牛客周赛 Round 28 20240114 总结A\B1. 题目描述2. 思路分析3. 代码实现 小红的炸砖块1. 题目描述2. 思路分析3. 代码实现 小红统计区间&#xff08;easy&#xff09;1. 题目描述2. 思路分析3. 代码实现 小红的好数组1. 题目描述2. 思路分析3. 代码实现 小红统…

FX图中的节点代表什么操作

在 FX 图中&#xff0c;每个节点代表一个操作。这些操作可以是函数调用、方法调用、模块实例调用&#xff0c;也可以是 torch.nn.Module 实例的调用。每个节点都对应一个调用站点&#xff0c;如运算符、方法和模块。 一.节点操作 下面是一些节点可能代表的操作&#xff1a; 1…

js中的数据类型

JavaScript 中有以下几种常见的数据类型&#xff1a; 基本类型&#xff08;原始类型&#xff09;&#xff1a; 字符串&#xff08;String&#xff09;&#xff1a;表示文本数据。数字&#xff08;Number&#xff09;&#xff1a;表示数值数据。布尔&#xff08;Boolean&#xf…

PPT插件-大珩助手-保留原素材的位置和大小一键替换

保留原素材的位置和大小一键替换 若勾选了一键替换&#xff0c;对于从素材库插入的图形&#xff0c;可以使得它的位置、大小与幻灯片中选中的形状一致 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持W…