NDK学习之路(一)访问静态域与乱码问题

文章内的代码均用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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值