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等),你需要使用相应的方法(如CallIntMethod
、CallFloatMethod
等)来调用。
另外,如果方法抛出了异常,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
,而Integer
在JNI
中却是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博客