一般在Android
中,对于JNI
的调用,基本的数据类型就能满足要求了,具体可以看我的这一篇文章 : Android JNI/NDK 入门从一到二,但是最近在项目中遇到了基本类型满足不了需要的情况,需要在JNI
中创建并操作Java类
,最后再返回到Java层
。
具体需求是这样的 : 需要同时返回坐标点和字符串,并且一次性返回的还不止一组,而是有N
组。
这么返回值就需要用Map<String,Array<Int>>
,或者使用Array<Java对象>
,那么具体需要怎么做呢 ? 我们下文一步一步来实现。
首先我们需要知道JNI
中各种类型的签名(可以理解为简写)是什么。
JNI
中提供了多种类型签名来表示Java
中的各种基本数据类型以及引用类型。
这些类型签名在JNI
中用于查找方法ID
、字段ID
以及其他与Java类型
交互的操作。
以下是主要的类型签名列表:
Z:boolean
B:byte
C:char
S:short
I:int
J:long
F:float
D:double
L<全限定类名>;
:对象或类实例。例如,对于java.lang.String
类,其签名是Ljava/lang/String;
。[:
数组标记,后面跟数组元素的类型签名。如[I
表示int
数组,[Ljava/lang/String;
表示String
数组。方法没有返回值时,用V
表示void
。
方法有返回值时,使用上述基本类型或引用类型的签名。
方法参数和返回值: 方法签名的格式是(参数1类型签名 参数2类型签名 ... 参数n类型签名)
返回值类型签名,例如:
(II)V // 表示一个接受两个int参数且无返回值的方法
(Ljava/lang/String;)[Ljava/lang/String; // 表示一个接受一个String参数并返回String数组的方法
于在Java
类加载器中查找指定的类,返回类型为jclass
jclass cls = env->FindClass("java类的包名/类名")
获取Java
类的指定方法的ID
,返回类型为jmethodID
jmethodID methodId = env->GetMethodID()
创建一个新的Java对象实例
jobject obj = env->NewObject()
用于调用Java对象的指定方法
jobject result = env->CallObjectMethod()
需要注意的是,
CallObjectMethod
方法返回的结果类型是jobject
,这意味着它返回的是Java对象的引用。如果方法返回的是原始类型(如int、float等),你需要使用相应的方法(如CallIntMethod
、CallFloatMethod
等)来调用。
另外,如果方法抛出了异常,CallObjectMethod
会返回一个空引用(nullptr
)。因此,在调用该方法后,通常需要检查返回值是否为空,以确定方法是否成功执行。
用于获取Java类的指定字段的ID
jfieldID fid = env->GetFieldID()
用于获取Java对象的指定整型字段的值,其他类型的值的获取也是类似的
jint value = env->GetIntField()
用于设置Java对象的指定整型字段的值,其他类型的值的设置也是类似的
void SetIntField(jobject obj, jfieldID fieldID, jint value)
进行调用
env->SetIntField(obj, filedId, value);
Map<String,Array<Int>>
首先来尝试下使用Map<String,Array<Int>>
返回
Map<String, String>
的返回要实现Map<String,Array<Int>>
,那么第一步就需要实现Map<String, String>
。
Map<String, String>
的实现肯定比Map<String,Array<Int>>
的实现简单。
external fun test1(): Map<String, String>
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;
}
val map = test1()
for (entry in map.entries) {
Log.i("ZZZZ", "key:${entry.key} value:${entry.value}")
}
打印结果
key:key1 value:value1
Map<String, Array<Int>>
的返回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
,而Integer
在JNI
中却是jobject
。
external fun test2(): Map<String, Array<Int>>
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;
}
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
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;
}
}
external fun test3(): JavaBean
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 int
jfieldID myIntFiledId = env->GetFieldID(clazz, "myPublicInt", "I");
jint myPublicInt = env->GetIntField(newObj, myIntFiledId);
LOGD("myPublicInt %d", myPublicInt);
env->SetIntField(newObj, myIntFiledId, index);
//赋值public float
jfieldID 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 string
jfieldID 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;
}
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]
Array<Java对象>
返回Array<Java对象>
,只需要在外面再套一层jobjectArray
就行了。
external fun test4(): Array<JavaBean>
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;
}
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:我的天!
external fun test5(): Array<IntArray>
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;
}
val array = test5()
Log.i("ZZZZ", "二维数组长度:" + array.size)
for (i in 0 until array.size) {
val childSize = array[i].size
var str = ""
for (j in 0 until childSize) {
str += " ${array[i][j]}"
}
Log.i("ZZZZ", "二维数组:${str}")
}
二维数组长度:5
二维数组: 0 1 2 3
二维数组: 4 5 6 7
二维数组: 8 9 10 11
二维数组: 12 13 14 15
二维数组: 16 17 18 19
我的其他和JNI
相关的文章 :
Android JNI/NDK 入门从一到二-CSDN博客
Android和JNI交互 : 常见的图像格式转换 : NV21、RGBA、Bitmap等_安卓代码图片格式转换-CSDN博客