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

来源:互联网 发布:正规的网络赚钱平台 编辑:程序博客网 时间:2024/05/17 09:03

通过之前的学习,知道了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);}
阅读全文
0 0
原创粉丝点击