Android JNI与Java类的转换调用

1. 前言

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

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

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>

首先来尝试下使用Map>返回

4.1 实现Map的返回

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

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, "", "()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>的返回

4.2.1 ArrayIntArray的区别

首先我们要明白ArrayIntArray的区别是什么。

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 t3();
    fun t3(): Map<String?, String?>?
    // Map t4();
    fun t4(): Map<String?, Array<Int?>?>?
    //Integer[] t5();
    fun t5(): Array<Int?>?
}

可以发现,kotlin中的Array对应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, "", "()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, "", "()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;
}

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

返回Array,只需要在外面再套一层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].size
    var 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. 其他

8.1 其他JNI文章

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

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

8.2 本文源码

地址 : gitee-MyNdkTest

你可能感兴趣的:(android,JNI,DNK,java类,转换,调用)