您的位置:首页 > 编程语言 > Java开发

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

2017-08-13 11:21 766 查看
通过之前的学习,知道了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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ndk Android
相关文章推荐