文章内的代码均用c编写(另,本人刚刚学习ndk开发,如有问题,希望指出)
1.jni访问java中的非静态方法,比如产生UUID,在C难以调用,在JAVA层产生更加方便
1.1获得类的id
1.2获得方法id //需要方法签名,可用javap -s -p <文件.class> 命令获得
1.3调用方法 call方法
JNIEXPORT void JNICALL
Java_com_test_jni_101_MainActivity_jniPass(
JNIEnv *env,
jobject obj) {
jclass clz = (*env) ->GetObjectClass(env,obj);
jmethodID methodId = (*env)->GetMethodID(env,clz,"jniToJava","(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env,obj,methodId,(*env)->NewStringUTF(env,"I Love Key"));
(*env)->DeleteLocalRef(env,clz);
}
2.访问静态方法,java中访问静态方法的之前,JVM会将静态方法加入到内存里面,
但JVM内存有限,不会加载所有的静态方法,所以java在调用静态方法的时候,会先判断是否已经加载静态文件,如果没有
则会用ClassLoader加载静态文件,如果找不到,则会出现:java.lang.ClassNotFoundException异常,通常出现在反射里 JNI的访问流程和JVM加载极其相像 //学习ClassLoader
2.1通过jobject搜索class,如果找到则将class转为jclass
2.2获得jmethodID
2.3调用
JNIEXPORT void JNICALL
Java_com_test_jni_101_MainActivity_jniStaticCall(
JNIEnv *env,
jobject obj){
jclass clz = (*env)->GetObjectClass(env,obj);
jmethodID methodId = (*env)->GetStaticMethodID(env,clz,"jniToStaticJava","(Ljava/lang/String;)V");
(*env)->CallStaticVoidMethod(env,clz,methodId,(*env)->NewStringUTF(env,"Key love me"));
(*env)->DeleteLocalRef(env,clz);
}
3.在C语言里打印或使用jstring
3.1将jstring转为char指针
4.访问java构造方法,使用场景,如创建Date,JVM已经加载了Date类,所以只需要通过find找到类,通过之前通过get获取到的类是因为给了C类的对象
4.1同样是创建jclass,这里是通过类的路径从JVM里找到对应的类
4.2找到构造方法,获得jmethodID 所有构造方法的名字都是<init>
4.3通过jmethodID创建jobject
4.4调用对象里的方法步骤
4.4.1 通过jclass获得jmethodID
4.4.2 通过jobject对象调用jmethodID
JNIEXPORT jlong JNICALL
Java_com_test_jni_101_MainActivity_jniToStructureJava(
JNIEnv *env,
jobject obj){
jclass clz = (*env)->FindClass(env,"java/util/Date");
jmethodID mth = (*env)->GetMethodID(env,clz,"<init>","()V");
jobject jobject1 = (*env)->NewObject(env,clz,mth);
jmethodID mth1 = (*env)->GetMethodID(env,clz,"getTime","()J");
jlong time = (*env)->CallLongMethod(env,jobject1,mth1);
return time;
}
GetStringUTFChar ,第三个参数的作用:传入isCopy指针,得到一个boolder值(主要为了获取值),为JNI_TRUE是则表示已经重新开辟了一个内存,也就是复制一份给C使用,所以只需要当它为true的时候释放内存(ReleaseStringUTFChars:该参数提供给jvm使用,用于通知jvm释放内存,如图所示)
JAVA传入的参数可能是复制过的参数,也就是说既使在c中释放内存地址内的参数,也可能只是改变了复制过的参数内存,而不是改变了java传入参数的内存,当参数为JNI_TRUE的时候,还需要使用ReleaseStringUTFChars参数通知jvm释放内存或提交到原始内存,所以ReleaseStringUTFChars参数其实并不需要每次都使用,可以仅限制到isCopy为JNI_TRUE的时候使用
5.java向C传参,java内部使用的utf-16 16bit的编码方式,无论中英文都使用两个字节,而C里则是用的utf-8 unicode的可变字长字符,asii码占一个字节,而中文则占三个字节,C++里则全部用ascii编码,中文使用gb2312编码,两个字符表示一个汉字,所以中文传入C会将两个字节转为三个字节,出现乱码,如图
图中,JNI先会将字符串转成UTF-8,再env通过GetStringChars或GetStringUTFChars直接转换编码为相应的编码,所以会导致编码因长度不符合而出现乱码
5.1 将java的String转成C语言的char指针,如果是中文,直接转换会是乱码
5.2 将jstring转换为C编码
5.2.1 获取该字符串的长度
5.2.2 将jstring转为jchar
5.2.3 开辟C语言char指针的空间,长度为jchar+1(内存需要初始化)
5.2.4 将jchar复制到char里面(window可使用WideCharToMultiByte方法)
JNIEXPORT void JNICALL
Java_com_test_jni_101_MainActivity_javaToJniParameter(
JNIEnv *env,
jobject obj,
jstring j_str){
const jchar *jc_str = (*env)->GetStringChars(env, j_str, NULL);
jsize length = (*env)->GetStringLength(env,j_str);
char *c_str = malloc(sizeof(char) * length + 2);
memset(c_str,0, sizeof(char) * length + 2);
memcpy(c_str,jc_str,sizeof(char) * length + 1);
}
看一下打印
恩,完美。。。。
好吧,看来我得找下原因,从上图来看,代码出问题的可能性不大,但还是有编码问题,或许可以换个方向讨论,代码没有问题,打印有问题,因为我java里的是utf-8编写的,而在java中打印正常,那么只有一种可能,那就是logcat显示的是utf-8编码,而C语言里却转成了utf-16的编码,所以打印是错误的乱码,我们可以实验一下
LOGI("%s",(*env)->GetStringUTFChars(env,j_str,NULL));
从之前的图可以看出GetStringUTFChars是转为utf-8的字符,打印反而是成功的,我传入jni的Strng本身就是UTF-8,JNI再转成UTF-8,再通过GetStringUTFChars转为UTF-8,所以中间根本没有进行转换,就好像是在java打印一样顺利,这就说明其实转换是正常的,只是logcat将UTF-16当UTF-8打印了而以
我们可以再进行如下运行
char* rtn = NULL;
jclass clsstring = (*env)->GetObjectClass(env,j_str);
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env,j_str, mid, strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
LOGI("%s",rtn);
GB2312依然是乱码,所以针对于上面的代码其实是正确的,只是打印不了GB2312的字符串而以
6.C中文传到java
6.1 调用java String的String(btye[] , String) 构造方法具体方法参考第4点
6.2 将jstring 转换成jbyteArray数组
6.2.1 开辟数组
6.2.2 用ENV方法将jstring转到byte数组里(Set方法)
6.3 得到对应的编码方式(NewStringUTF(env,"GB2312"))
6.4 调用构造方法
JNIEXPORT jobject JNICALL
Java_com_test_jni_101_MainActivity_jniToJavaParameter(
JNIEnv *env,
jobject obj){
jclass clz = (*env)->FindClass(env,"java/lang/String");
jmethodID mth = (*env)->GetMethodID(env,clz,"<init>","([BLjava/lang/String;)V");
const char *c_str = "旭宝宝";
jcharArray arr = (*env)->NewByteArray(env,strlen(c_str));
(*env)->SetByteArrayRegion(env,arr,0,strlen(c_str),c_str);
jstring str = (*env)->NewStringUTF(env,"UTF-8");
jobject obj1 = (*env)->NewObject(env,clz,mth,arr,str);
return obj1;
}