• JNI 的数据类型以及和Java层之间的数据转换


    JNI的数据类型和类型签名

    数据类型

    JNI的数据类型包含两种:基本类型引用类型

    基本类型主要有jbooleanjcharjint等,它们和Java中的数据类型的对应关系如下表所示。

    在这里插入图片描述

    JNI中的引用类型主要有类、对象和数组,它们和Java中的引用类型的对应关系如下表所示。

    在这里插入图片描述

    当然,JNI 中还有个 Java 中没有的 jsize,定义如下:

    typedef jint jsize;
    
    • 1

    其实jsize整型是用来描述基本指标和大小,没有什么神秘的。

    类型签名

    JNI的类型签名标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。

    类的签名比较简单,它采用 L+包名+类名+; 的形式,只需要将其中的替换为/即可。比如java.lang.String,它的签名为Ljava/lang/String;,注意末尾的也是签名的一部分。

    基本数据类型的签名采用一系列大写字母来表示,如下表所示。

    在这里插入图片描述

    从上表可以看出,基本数据类型的签名是有规律的,一般为首字母的大写,但是boolean除外,因为B已经被byte占用了,而long的签名之所以不是L,那是因为L表示的是类的签名。

    对象和数组的签名稍微复杂一些。对于对象来说,它的签名就是对象所属的类的签名,比如String对象,它的签名为Ljava/lang/String;。对于数组来说,它的签名为[+类型签名,比如int数组,其类型为int,而int的签名为I,所以int数组的签名就是[I,同理就可以得出如下的签名对应关系:

    char[]       [C
    float[]      [F
    double[]     [D
    long[]       [J
    String[]     [Ljava/lang/String;
    Object[]     [Ljava/lang/Object;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对于多维数组来说,它的签名为n个[+类型签名,其中n表示数组的维度,比如,int[][]的签名为[[I,其他情况可以依此类推。

    方法的签名为(参数类型签名)+返回值类型签名,这有点不好理解。举个例子,如下方法:boolean fun1(int a, double b, int[] c),根据签名的规则可以知道,它的参数类型的签名连在一起是ID[I,返回值类型的签名为Z,所以整个方法的签名就是(ID[I)Z。再举个例子,下面的方法:boolean fun1(int a, String b, int[] c),它的签名是(ILjava/lang/String; [I)Z。为了能够更好地理解方法的签名格式,下面再给出两个示例:

    int fun1()        签名为 ()I
    void fun1(int i)  签名为 (I)V
    
    • 1
    • 2

    一个Java类的方法的Signature可以通过javap命令获取:javap -s -p Java类名

    本地方法中访问java程序中的内容

    1. 访问 String 对象

    从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做 char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv的方法转换。下面是一个例子:

    JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
    {
      char buf[128];
      const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
      printf("%s", str);
      (*env)->ReleaseStringUTFChars(env, prompt, str);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里使用GetStringUTFChars方法将传进来的promptjstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。

    注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。

    下面是访问String的一些方法:

    • GetStringUTFCharsjstring转换成为UTF-8格式的char*
    • GetStringCharsjstring转换成为Unicode格式的char*
    • ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
    • ReleaseStringChars释放指向Unicode格式的char*的指针
    • NewStringUTF创建一个UTF-8格式的String对象
    • NewString创建一个Unicode格式的String对象
    • GetStringUTFLength获取UTF-8格式的char*的长度
    • GetStringLength获取Unicode格式的char*的长度

    2. 访问 Array 对象

    和String对象一样,在本地方法中不能直接访问jarray对象,而是使用JNIEnv指针指向的一些方法来使用。
      
    访问Java原始类型数组:

    • 1)获取数组的长度:
    JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
    {
     	int i, sum = 0;
      jsize len = (*env)->GetArrayLength(env, arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里获取数组的长度和普通的c语言中的获取数组长度不一样,这里使用JNIEnv的一个函数GetArrayLength

    • 2)获取一个指向数组元素的指针:
    jint *body = (*env)->GetIntArrayElements(env, arr, 0);
    
    • 1

    使用GetIntArrayElements方法获取指向arr数组元素的指针,注意该函数的参数,第一个是JNIEnv,第二个是数组,第三个是数组里面开始的元素。

    • 3)使用指针取出 Array 中的元素
    for (i=0; i<len; i++) {
     sum += body[i];
    }
    
    • 1
    • 2
    • 3

    这里使用就和普通的c中的数组使用没有什么不同了

    • 4)释放数组元素的引用
    (*env)->ReleaseIntArrayElements(env, arr, body, 0);
    
    • 1

    和操作String中的释放String的引用是一样的,提醒JVM回收arr数组元素的引用。

    这里举的例子是使用int数组的,同样还有boolean、float等对应的数组。

    获取数组和释放数组元素指针的对应关系:

    数组类型获取函数释放函数
    booleanGetBooleanArrayElementsReleaseBooleanArrayElements
    byteGetByteArrayElementsReleaseByteArrayElements
    charGetCharArrayElementsReleaseCharArrayElements
    shortGetShortArrayElementsReleaseShortArrayElements
    intGetIntArrayElementsReleaseIntArrayElements
    longGetLongArrayElementsReleaseLongArrayElements
    floatGetFloatArrayElementsReleaseFloatArrayElements
    doubleGetDoubleArrayElementsReleaseDoubleArrayElements

    • GetObjectArrayElement returns the object element at a given index.
    • SetObjectArrayElement updates the object element at a given index.

    3. 访问Java对象的方法

    JNI调用Java方法的流程是先通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了。如果是调用Java中的非静态方法,那么需要构造出类的对象后才能调用它。
      
    在本地方法中调用Java对象的方法的步骤:
      
    ① 获取你需要访问的Java对象的Class类:

    jclass cls = (*env)->GetObjectClass(env, obj);
    
    • 1

    使用GetObjectClass方法获取obj对应的jclass
      
    ② 获取MethodID

    jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
    
    • 1

    使用GetMethdoID方法获取你要使用的方法的MethdoID。其参数的意义:

    • envJNIEnv
    • cls:第一步获取的jclass
    • "callback":要调用的方法名
    • "(I)V":方法的Signature

    ③ 调用方法:

    (*env)->CallVoidMethod(env, obj, mid, depth);
    
    • 1

    使用CallVoidMethod方法调用方法。参数的意义:

    • envJNIEnv指针
    • obj:调用该native方法的jobject对象
    • mid:方法的methodID(即第二步获得的MethodID
    • depth:方法需要的参数(对应方法的需求,添加相应的参数),可以是可变参数

    注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话要使用对应的方法。

    除了CallVoidMethod外,针对每种基本类型的方法都有不同的重载,如下:

    jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);  
    jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
    void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    给调用的函数传参数:

    通常我们直接在methodID后面将要传的参数添加在后面,但是还有其他的方法也可以传参数:

    • CallVoidMethodV可以获取一个数量可变的列表作为参数;
    • CallVoidMethodA可以获取一个union。

    调用静态方法:

    就是将第二步和第三步调用的方法改为对应的:

    • GetStaticMethodID获取对应的静态方法的ID
    • CallStaticIntMethod调用静态方法

    调用静态方法应该使用对应的CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变(参考前面非静态方法列出的类型)。

    // 首先需要在Java中定义一个静态方法供JNI调用,如下所示。
    public static void methodCalledByJni(String msgFromJni) {
        Log.d(TAG, "methodCalledByJni, msg: " + msgFromJni);
    }
    // 然后在JNI中调用上面定义的静态方法:
    void callJavaMethod(JNIEnv *env, jobject thiz) {
        jclass clazz = env->FindClass("com/ryg/JniTestApp/MainActivity");
        if (clazz == NULL) {
          printf("find class MainActivity error! ");
          return;
        }
        jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni",
        "(Ljava/lang/String; )V");
        if (id == NULL) {
          printf("find method methodCalledByJni error! ");
        }
        jstring msg = env->NewStringUTF("msg send by callJavaMethod in
        test.cpp.");
        env->CallStaticVoidMethod(clazz, id, msg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4. 访问Java对象的属性

    访问Java对象的属性和访问Java对象的方法基本上一样,只需要将函数里面的Method改为Field即可。

    • GetFieldID:获取某个属性的id
    • GetStaticFieldID:获取某个静态属性的id

    5. 访问Java对象的Class对象

    为了能够在C/C++中调用Java中的类,jni.h的头文件专门定义了jclass类型表示Java中Class类。JNIEnv中有3个函数可以获取jclass

    • jclass FindClass(const char* clsName):通过类的名称来获取jclass。注意,是类的全名,这时候包名不是用’".“点号而是用”/"来区分的。

      比如: jclass jcl_string=env->FindClass("java/lang/String");来获取Java中的String对象的class对象

    • jclass GetObjectClass(jobject obj):通过对象实例来获取jclass,相当于Java中的getClass()函数

    • jclass getSuperClass(jclass obj):通过jclass可以获取其父类的jclass对象


    JNI 和 Java 层之间的数据传输

    1 基本数据类型的传输

    上层定义一个native的方法,需要一个int 参数 ,返回一个int值;JNI 对应上层的方法 , 打印出上层 传输下来的 int数据,并返回 int数据。

    上层 收到 native 方法 返回的 值,在UI中显示出来

    public native int getNumber(int num);
    
    • 1
    jint Java_XX_XX_XXActivity_getNumber(JNIEnv* env,jobject thiz,jint num)
    {
        if(jniEnv == NULL) {
            jniEnv = env;
        }
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Java -- > C JNI : num = %d",num);
        return num*2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:jintint的互转都可以直接使用强转,如:jint i = (jint) 10;

    2 数组的传输

    上层定义一个native的方法,需要一个int数组,返回一个int数组;JNI 对应上层的方法,取出上层传递数组中的数据处理和打印出来,并存入新数组中,最后把该数组返回给 Java层。

    上层 收到 native返回的 数组,加工成字符串,在UI中显示出来

    public native int[] getArrayNumber(int[] nums);
    
    • 1
    JNIEnv* jniEnv;
    jintArray Java_XX_XX_XXActivity_getArrayNumber(JNIEnv* env,jobject thiz,jintArray nums)
    {
        if(jniEnv == NULL) {
            jniEnv = env;
        }
        if(nums == NULL){
            return NULL;
        }
        jsize len = (*jniEnv)->GetArrayLength(jniEnv, nums);
        if(len <= 0) {
            return NULL;
        }
        jintArray array = (*jniEnv)->NewIntArray(jniEnv, len);
        if(array == NULL) {
            return NULL;
        }
        // 把 Java 传递下来的数组 用 jint* 存起来
        jint *body = (*env)->GetIntArrayElements(env, nums, 0);
        jint i = 0;
        jint num[len];
        for (; i < len; i++) {
            num[i] = body[i] * 2;
        }
        if(num == NULL){
            return NULL;
        }
    	//(*env)->GetIntArrayRegion(env,array,start,len,buffer)
    	// 从start开始复制长度为len 的数据到buffer中
        //给需要返回的数组赋值
        (*jniEnv)->SetIntArrayRegion(jniEnv,array, 0, len, num);
        return array;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    对于其他类型数组,使用对应类型的成对方法读取和设置,如byte数组可使用 NewByteArray();SetByteArrayRegion();

    3 引用数据类型

    String 字符串传输

    上层定义一个native的方法,需要一个String 参数,返回一个String;JNI对应上层的方法,打印出上层传输下来的String数据,并返回处理String数据。

    上层 收到 native 方法 返回的 值,在UI中显示出来

    public native String transferString(String mStrMSG);
    
    • 1
    jstring Java_XX_XX_XXActivity_transferString(JNIEnv* env,jobject thiz,jstring msg)
    {
        if(jniEnv == NULL) {
            jniEnv = env;
        }
        char data[128];
        memset(data, 0, sizeof(data));
        char *c_msg = NULL;
        c_msg = (char *)(*jniEnv)->GetStringUTFChars(jniEnv, msg, 0);
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI  ---- > %s",c_msg);
        return (*jniEnv)->NewStringUTF(jniEnv, "This is send by C JNI");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    自定义对象的传输

    自定义一个对象Person,上层定义一个native方法,参数Person,返回值Person;JNI接收对象,打印出相关信息数据,JNI 修改 Person 对象数据,并返回到上层。

    上层接收到数据后 在UI显示出来

    public native Object transferPerson(Person mPerson);     
    
    • 1
    public class Person {
        private String name;
        private int age;
        public Person() {
            name = "";
            age = 0;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    extern JNIEnv* jniEnv;
    jclass Person;
    jobject mPerson;
    jmethodID getName;
    jmethodID setName;
    jmethodID toString;
    int InitPerson();
    void ToString();
    void GetName();
    void SetName();
     
    jobject Java_XX_XX_XXActivity_transferPerson(JNIEnv* env,jobject thiz,jobject person)
    {
        if(jniEnv == NULL) {
            jniEnv = env;
        }
        if (Person == NULL || getName == NULL || setName == NULL || toString == NULL) {
            if (1 != InitPerson()) {
                return NULL;
            }
        }
        mPerson = person;
        if(mPerson == NULL) {
            return NULL;
        }
        GetName();
        GetAge();
        ToString();
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Begin Modify mPerson  .... ");
        SetName();
        SetAge();
        ToString();
        return mPerson;
    }
     
    int InitPerson() {
        if(jniEnv == NULL) {
            return 0;
        }
        if(Person == NULL) {
            Person = (*jniEnv)->FindClass(jniEnv,"com/XX/Person");
            if(Person == NULL){
                return -1;
            }
        }
        if (getName == NULL) {
            getName = (*jniEnv)->GetMethodID(jniEnv, Person, "getName","()Ljava/lang/String;");
            if (getName == NULL) {
                (*jniEnv)->DeleteLocalRef(jniEnv, Person);
                return -2;
            }
        }
        if (setName == NULL) {
            setName = (*jniEnv)->GetMethodID(jniEnv, Person, "setName","(Ljava/lang/String;)V");
            if (setName == NULL) {
                (*jniEnv)->DeleteLocalRef(jniEnv, Person);
                (*jniEnv)->DeleteLocalRef(jniEnv, getName);
                return -2;
            }
        }
        if (getAge == NULL) {
            getAge = (*jniEnv)->GetMethodID(jniEnv, Person, "getAge","()I");
            if (getAge == NULL) {
                (*jniEnv)->DeleteLocalRef(jniEnv, Person);
                (*jniEnv)->DeleteLocalRef(jniEnv, getName);
                (*jniEnv)->DeleteLocalRef(jniEnv, setName);
                return -2;
            }
        }
        if (setAge == NULL) {
            setAge = (*jniEnv)->GetMethodID(jniEnv, Person, "setAge","(I)V");
            if (setAge == NULL) {
                (*jniEnv)->DeleteLocalRef(jniEnv, Person);
                (*jniEnv)->DeleteLocalRef(jniEnv, getName);
                (*jniEnv)->DeleteLocalRef(jniEnv, setName);
                (*jniEnv)->DeleteLocalRef(jniEnv, getAge);
                return -2;
            }
        }
        if (toString == NULL) {
            toString = (*jniEnv)->GetMethodID(jniEnv, Person, "toString","()Ljava/lang/String;");
            if (toString == NULL) {
                (*jniEnv)->DeleteLocalRef(jniEnv, Person);
                (*jniEnv)->DeleteLocalRef(jniEnv, getName);
                (*jniEnv)->DeleteLocalRef(jniEnv, setName);
                (*jniEnv)->DeleteLocalRef(jniEnv, getAge);
                (*jniEnv)->DeleteLocalRef(jniEnv, setAge);
                return -2;
            }
        }
        return 1;
    }
    /**
    * GetName  对应Person的getName方法
    */
    void GetName() {
        if(Person == NULL || getName == NULL) {
            if(1 != InitPerson()){
                return;
            }
        }
        //调用方法
        jstring jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
        char* cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getName  ---- >  %s",cstr );
        //释放资源
        (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
        (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
    }
    /**
    * GetAge 对应Person的getName方法
    */
    void GetAge() {
        if(Person == NULL || getName == NULL) {
            if(1 != InitPerson()){
                return;
            }
        }
        //调用方法
        jint age = (*jniEnv)->CallIntMethod(jniEnv, mPerson, getAge);
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getAge  ---- >  %d",age );
    }
    /**
    * SetName 对应Person的setName方法
    */
    void SetName() {
        if(Person == NULL || setName == NULL) {
            if(1 != InitPerson()){
                return;
            }
        }
        jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, "Modify Name");
        //调用方法
        (*jniEnv)->CallVoidMethod(jniEnv, mPerson, setName,jstr);
        (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
    }
    int age = 20;
    /**
    * SetAge 对应Person的setAge方法
    */
    void SetAge() {
        if(Person == NULL || setAge == NULL) {
            if(1 != InitPerson()){
                return;
            }
        }
        //调用方法
        (*jniEnv)->CallVoidMethod(jniEnv, mPerson, setAge,age++);
    }
    /**
    * ToString 对应 Person 的 toString 方法 , 打印出相关信息
    */
    void ToString() {
        if(Person == NULL || toString == NULL) {
            if(1 != InitPerson()){
                return;
            }
        }
        jstring jstr = NULL;
        char* cstr = NULL;
        //调用方法
        jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, toString);
        cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
        __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI toString  ---- >  %s",cstr );
        (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
        (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
  • 相关阅读:
    Apifox能否替代Postman?
    TCP 面向字节流、延迟应答、异常情况
    EPLAN小知识——如何在费斯托(FESTO)官网下载EPLAN部件
    秋风起,硕果丰!菊风视频能力平台R22C03版本重磅发布
    Typescript入门知识
    2021 ICPC澳门题解(8/11)
    PLC网关用途、解决问题以及如何实现高效、稳定通信分享
    大白话JS中Object.entires()和Object.assign()的使用
    代码随想录算法训练营第59天|● 503.下一个更大元素II ● 42. 接雨水
    【乳腺超声、乳腺钼靶、宫颈癌】等项目数据调研,及相关参考内容整理汇总
  • 原文地址:https://blog.csdn.net/lyabc123456/article/details/133850724