NDK开发学习笔记(2):JNI访问Java中的方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011118524/article/details/77141118

通过之前的学习,知道了jni函数的调用流程以及在jni中访问java的静态字段和非静态字段,接下来将继续学习JNI中访问java中的各种方法。基本步骤遵循JNI的开发流程(参考:NDK开发学习笔记(1):JNI开发步骤及遇到的问题详解),JNI中调用java方法的基本流程:

  • (1)通过对象找到类:
//jclass 通过jobject来搜索class(搜索的过程由jvm来完成),如果找到了,将这个class转变成jclass,然后返回
jclass jclz = (*env)->GetObjectClass(env, jobj);
  • (2)根据类、方法名称和方法签名(获取方式参考下面:方法签名的获取)得到方法的id:
    ////jmethodid  获取方法的id,这里调用的是java中的静态方法
    jmethodID methodId = (*env)->GetMethodID(env, jclz, "getRandomInt", "(I)I");
  • (3)根据方法id调用java中的方法
    ////根据方法id调用java中对应的方法,参数:jobj对象,methodId方法id,200java方法需要传入的参数
    jint random = (*env)->CallIntMethod(env, jobj, methodId, 200);

1、JNI访问java中的非静态方法:
如java代码:

   /**
     * 在jni中访问java中的非静态方法,即在jni中,调用java的方法,
     * 如:这里访问getRandomInt()方法
     */
    public native void accessMethod();

    int getRandomInt(int max){
        return new Random().nextInt(max);
    }

生成头文件并实现方法:

/*
*JNI中访问java中的非静态方法
*/
JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_accessMethod
(JNIEnv *env, jobject jobj) {
    //jclass
    jclass jclz = (*env)->GetObjectClass(env, jobj);
    //根据jclass得到方法id
    jmethodID methodId = (*env)->GetMethodID(env, jclz, "getRandomInt", "(I)I");
    //根据方法id调用对应的java方法
    jint random = (*env)->CallIntMethod(env, jobj, methodId, 200);
    printf("C random:%d\n", random);
}

通过方法GetMethodID(JNIEnv *env, jclass clazz,
const char *name, //需要调用java中的方法的方法名称
const char *sig //方法签名
);方法获取java中方法的id,需要知道方法的方法签名。

方法签名的获取:

  • (1)通过javap命令查询:
    在cmd中,把目录切换到bin目录下执行javap -s -p 包名.类名或者对应的java类的class文件目录下执行javap -s -p 类名,如:
    这里写图片描述
    descriptor指向的值就是jni所需要的方法签名,如:本例中jni需要调用java中的getRandomInt方法,如图所知getRandomInt方法的签名为:(I)I。
  • (2)通过经验和规律自己编写,公式:(参数类型)返回值类型。

    2、JNI访问java中的静态方法:与访问非静态方法的区别在于调用的方法都加有static关键词

    ////jclass 通过jobject来搜索class(搜索的过程由jvm来完成),如果找到了,将这个class转变成jclass,然后返回
    jclass jclz = (*env)->GetObjectClass(env, jobj);
    //jmethodid  获取方法的id
    jmethodID mid = (*env)->GetStaticMethodID(env, jclz, "getRandomUUID", "()Ljava/lang/String;");
    //根据方法id调用java中对应的方法
    jstring uuid = (*env)->CallStaticObjectMethod(env, jclz, mid);

3、JNI访问java中的构造函数:
访问步骤:

  • (1)根据类路径,找到类(从JVM里找到对应的类)
  • (2)获取构造函数的id ,所有的构造函数方法名都传这里写图片描述
  • (3)根据构造函数id实例化对象
  • (4)获取要调用的方法的方法id
  • (5)根据方法id和对象调用对应对象的的方法

    实例如下:

/*
* 在jni中访问java的构造函数
*/
JNIEXPORT jobject JNICALL Java_com_mei_test_jni_JniTest_accessConstructor
(JNIEnv *env, jobject jobj) {
    //1、根据类路径,找到类
    //通过类的路径,从JVM里找到对应的类
    jclass jclz = (*env)->FindClass(env, "java/util/Date");
    //2、获取构造函数的id  ,所有的构造方法都传"<init>"
    //jmethodid,,这里调用的是Date的无参构造函数
    jmethodID jmid = (*env)->GetMethodID(env, jclz, "<init>", "()V");

    //3、实例化对象
    //调用NewObject实例化一个java对象,返回值是一个jobject,jni把所有的引用类型都转化成了jobject
    jobject date_obj = (*env)->NewObject(env, jclz, jmid);

    //4、获取要调用的方法的id
    //调用Date的getTime方法,
    //获取方法的id,得到对应对象的方法id,前提是我们访问了相关对象的构造函数并创建了这个对象
    jmethodID time_mid = (*env)->GetMethodID(env, jclz, "getTime", "()J");

    //5、根据方法id调用对应对象的的方法
    //调用date的getTime方法
    jlong time = (*env)->CallLongMethod(env, date_obj, time_mid);

    printf("time:%lld\n", time);

    return date_obj;
}

4、JNI与java交互时遇到的乱码问题:

  • 在java中,字符的编码使用的是utf-16,即每个字符占用16bit,即2个字节,不管是中文还是英文。
  • 在JNI中,字符采用的编码是utf-8 ,即可变字节的方式,一般ascii字符是1字节,中文占用3个字节。
    -在c/c++使用的是原始数据,ascii字符就是一个字节,中文采用GB2312编码,用2个字节表示一个汉字。

    从java String转换成C String的流程:

    这里写图片描述
    从图可知,String从java传递到jni层的时候,被转换成了UTF-8的编码格式,此时的String类型变成了jstring(jni的数据类型),当我们需要在c/c++中使用这个字符串的时候,需要使用C/C++的GetStringChars或者GetStringUTFChars方法,把jstring转换成c/c++的字符串类型char*,如果字符中包含有中文,就用GetStringUTFChars方法得到c的String类型,保证编码一致。如果直接在c文件中定义一个中文字符串,则需要用GB2312编码来返回给java才不会乱码。
    如:

/*
*////中文乱码处理2  android中推荐使用此方法
*/
jobject chineseHandle(JNIEnv *env,jstring str) {
    //char *c_str = "利用Java中的String类进行乱码处理:中国梦,真伟大";
    char *c_str = (*env)->GetStringUTFChars(env, str, NULL);

    jclass str_clz = (*env)->FindClass(env, "java/lang/String");
    jmethodID jmid = (*env)->GetMethodID(env, str_clz, "<init>", "([BLjava/lang/String;)V");

    jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
    //将char *赋值到bytes
    //把c中的string从0开始到字符长度的内容,全部拷贝到数组bytes中
    (*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);

    //jstring charsetName = (*env)->NewStringUTF(env, "GB2312");//如果是在c中定义的中文字符串,则用GB2312
    jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");

    jobject str_obj = (*env)->NewObject(env, str_clz, jmid, bytes, charsetName);
    return str_obj;
}

在Window平台还有另外一种解决乱码的方式,代码如下:

/*
* 中文乱码问题
*/
JNIEXPORT jstring JNICALL Java_com_mei_test_jni_JniTest_chineseChars
(JNIEnv *env, jobject jobj, jstring in){

    jboolean iscp;
    /*
    *第三个参数也是返回值的一部分,在GetStringUTFChars的内部被赋值,
    *jni在使用java传进来的String的时候,如果是直接使用的话,那么iscp就会被赋值为false,
    *如果是重新开辟一块内存空间把String复制一份,并使用备份的这个String的话,iscp就会被赋值为true,
    *那么我们在外面可以根据这个值来判断,是否需要释放内存空间。
    *可以传NULL
    */
    char *c_str = (*env)->GetStringUTFChars(env, in, &iscp);
    printf("没有处理乱码前 C String:%s:\n", c_str);
    if (iscp == JNI_TRUE) {
        printf("is copy:true\n");
    }else if (iscp == JNI_FALSE) {
        printf("is copy:false\n");
    }

    int length = (*env)->GetStringLength(env, in);
    const jchar * jcstr = (*env)->GetStringChars(env, in, NULL);
    if (jcstr == NULL) {
        printf("jcstr is NULL");
        return NULL;
    }
    //jchar->char
    char *rtn = (char *)malloc(sizeof(char)*length*2 + 1);
    memset(rtn, 0, sizeof(char)*length*2 + 1);
    int size = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)jcstr, length, rtn, sizeof(char)*length*2 + 1,NULL,NULL );
    if (size <= 0) {
        printf("size 0");
        return NULL;
    }

    printf("乱码处理后的C String:%s\n", rtn);

    if (iscp == JNI_TRUE) {
        //只有在复制重新开辟内存空间的时候,才去释放内存空间
        (*env)->ReleaseStringUTFChars(env, in, c_str);
    }

    //如果GetStringUTFChars(env, in, NULL);第三个参数传的是NULL的话,可以用此方法来释放内存空间
    //(*env)->ReleaseStringChars(env, in, c_str);//JVM使用,通知JVM c_str所指的内存空间可以释放了了

    if (rtn != NULL) {
        free(rtn);
        rtn = NULL;
    }

    return chineseHandle(env,in);
}

猜你喜欢

转载自blog.csdn.net/u011118524/article/details/77141118