NDK开发学习笔记(2):JNI访问Java中的方法
2017-08-13 11:21
766 查看
通过之前的学习,知道了jni函数的调用流程以及在jni中访问java的静态字段和非静态字段,接下来将继续学习JNI中访问java中的各种方法。基本步骤遵循JNI的开发流程(参考:NDK开发学习笔记(1):JNI开发步骤及遇到的问题详解),JNI中调用java方法的基本流程:
(1)通过对象找到类:
(2)根据类、方法名称和方法签名(获取方式参考下面:方法签名的获取)得到方法的id:
(3)根据方法id调用java中的方法
1、JNI访问java中的非静态方法:
如java代码:
生成头文件并实现方法:
通过方法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关键词
3、JNI访问java中的构造函数:
访问步骤:
(1)根据类路径,找到类(从JVM里找到对应的类)
(2)获取构造函数的id ,所有的构造函数方法名都传
(3)根据构造函数id实例化对象
(4)获取要调用的方法的方法id
(5)根据方法id和对象调用对应对象的的方法
实例如下:
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才不会乱码。
如:
在Window平台还有另外一种解决乱码的方式,代码如下:
(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); }
相关文章推荐
- JNI学习笔记:(1)开篇(2)本地代码访问Java代码 (3)本地方法取得Java属性/调用java方法 (4)本地代码创建Java对象(包括javaString) (5) 本地方法处理java数组
- JNI学习笔记:C++代码访问Java类中的成员和方法
- NDK开发学习笔记(3):JNI访问数组、引用、异常处理、缓存策略
- 【学习Android NDK开发】Java通过JNI调用native方法
- Android(java)学习笔记259:JNI之NDK开发步骤
- JNI/NDK开发指南(六)——C/C++访问Java实例方法和静态方法
- JNI/NDK开发指南(六)——C/C++访问Java实例方法和静态方法
- NDK开发学习笔记—C/C++访问java成员
- JNI学习笔记(四)JNI中本地语言创建Java对象并且访问具体方法(附例子)
- JNI 学习笔记(二)-- JNI访问Java中各方法
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- 【学习Android NDK开发】native code通过JNI调用Java方法
- Android(java)学习笔记256:JNI之NDK的概念
- Java学习笔记11:在公共类中使用访问方法而非公有域
- Android NDK (学习笔记八) —— Java代码与C代码间方法的调用
- Android-java调用本地方法返回字符串显示在界面上/NDK-JNI开发实例(二)
- NDK开发笔记(二)---JNI的学习
- Android-本地方法C调用Java中的方法/NDK-JNI开发实例(六)
- Android NDK(学习笔记四)—— 在NDK开发中JNI打印Log信息
- JNI学习之---用jni API 访问java对象的属性,方法,调用构造器。