美文网首页
JNI 常见用法

JNI 常见用法

作者: feifei_fly | 来源:发表于2018-11-10 22:13 被阅读0次

    一、Java 代码 和JNI代码通信

    Java代码通过JNI接口 调用 C/C++方法

    1、首先我们需要在Java代码中声明Natvie方法原型

    public native void helloJNI(String msg);
    

    2、其次我们需要在C/C++代码里声明JNI方法的原型
    如:

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) {
        //do something
    }
    
    • extern "C"。JNI函数声明声明代码是用C++语言写的,所以需要添加extern "C"声明;如果源代码是C语言声明,则不需要添加这个声明
    • JNIEXPORT。这个关键字表明这个函数是一个可导出函数。每一个C/C++库都有一个导出函数列表,只有在这个列表里面的函数才可以被外部直接调用,类似Java的public函数和private函数的区别。
    • JNICALL。说明这个函数是一个JNI函数,用来和普通的C/C++函数进行区别。
    • Void 返回值类型
    • JNI函数名原型:Java_ + JNI方法所在的完整的类名,把类名里面的”.”替换成”_” + 真实的JNI方法名,这个方法名要和Java代码里面声明的JNI方法名一样。
    • env 参数 是一个执行JNIENV函数表的指针。
    • thiz 参数 代表的是声明这个JNI方法的Java类的引用。
    • msg 参数就是和Java声明的JNI函数的msg参数对于的JNI函数参数

    静态JNI方法 和实例JNI方法的区别

    Java代码:

    public native void showHello();
    public native static void showHello2();
    

    C++代码:

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_kgdwbb_jnistudy_MainActivity_showHello(JNIEnv* env, jobject thiz) {
        //do something
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_kgdwbb_jnistudy_MainActivity_showHello2(JNIEnv* env, jclass thiz) {
        //do something
    }
    
    

    二、java 和JNI类型对照表

    Java 和JNI基本类型对照表

    java的基本类型可以直接与C/C++的基本类型映射。

    image

    Java与JNI引用类型对照表

    与Java基本类型不同,引用类型对开发人员是不透明的。Java内部数据结构并不直接向原生代码开放。也就是说 C/C++代码并不能直接访问Java代码的字段和方法

    image

    三、JNI 基本操作举例

    1、JNI操作 字符串

    java 类 TestNatvie.java

      /**
         * 字符串相关测试代码
         * @param str
         */
        public native void testJstring(String str);
    

    C++文件 natvie-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testJstring(JNIEnv *env, jobject instance,
                                                           jstring str_) {
        
      //(1)生成JNI String
        char const * str = "hello world!";
        jstring  jstring = env->NewStringUTF(str);
    
        // (2) jstring 转换成 const char * charstr
        const char *charstr = env->GetStringUTFChars(str_, 0);
        // (3) 释放 const char *
        env->ReleaseStringUTFChars(str_, charstr);
    
        //(4) 获取字符串子集
        char * subStr = new char;
        env->GetStringUTFRegion(str_,0,3,subStr);//截取字符串char*;
    
    
        env->ReleaseStringUTFChars(str_, subStr);
        
    }
    
    

    2、JNI操作数组

    java 类 TestNatvie.java

     /**
         * 整形数组相关代码
         * @param array
         */
        public native void testIntArray(int []array);
    
        /**
         *
         * Object Array 相关测试 代码
         * @param strArr
         */
        public native void testObjectArray(String[]strArr);
    

    C++文件 natvie-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testIntArray(JNIEnv *env, jobject instance,
                                                         jintArray array_) {
    
        //----获取数组元素
        //(1)获取数组中元素
        jint * intArray = env->GetIntArrayElements(array_,NULL);
    
        int len = env->GetArrayLength(array_);//(2)获取数组长度
    
        LOGD("feifei len:%d",len);
    
        for(int i = 0; i < len;i++){
            jint item = intArray[i];
            LOGD("feifei item[%d]:%d",i,item);
        }
    
        env->ReleaseIntArrayElements(array_, intArray, 0);
    
        //----- 获取子数组
        jint *subArray = new jint;
        env->GetIntArrayRegion(array_,0,3,subArray);
        for(int i = 0;i<3;i++){
            subArray[i]= subArray[i]+5;
            LOGD("feifei subArray:[%d]:",subArray[i]);
        }
    
        //用子数组修改原数组元素
        env->SetIntArrayRegion(array_,0,3,subArray);
    
        env->ReleaseIntArrayElements(array_,subArray,0);//释放子数组元素
    
    
    }
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testObjectArray(JNIEnv *env, jobject instance,
    
                                                               jobjectArray strArr) {
        //获取数组长度
        int len = env->GetArrayLength(strArr);
        for(int i = 0;i< len;i++){
            //获取Object数组元素
            jstring item = (jstring)env->GetObjectArrayElement(strArr,i);
    
            const char * charStr = env->GetStringUTFChars(item, false);
            LOGD("feifei strArray item:%s",charStr);
    
            jstring jresult = env->NewStringUTF("HaHa");
            //设置Object数组元素
            env->SetObjectArrayElement(strArr,i,jresult);
            env->ReleaseStringUTFChars(item,charStr);
        }
    
    }
    
    

    3、JNI 访问Java类的方法和字段

    JNI 中访问java类的方法和字段都是 通过反射来实现的。

    JNI获取Java类的方法ID和字段ID,都需要一个很重要的参数,就是Java类的方法和字段的签名

    image

    JNI 中访问Java对象的属性 和方法:

    java 类 TestNatvie.java

    public class TestNatvie {
        static {
            System.loadLibrary("native-lib");
        }
        
         /**
         * Jni调用 java 对象方法
         */
        public native void testCallJavaMethod();
          /**
         * Jni 调用 java static 方法
         */
        public native void testCallStaticJavaMethod();
         /**
         * JNI 访问 java 的对象属性和类属性
         * @param student
         */
        public native void getJavaObjectField(Student student);
        
         public void helloworld(String msg){
            Log.d("feifei","hello world:"+msg);
        }
    
        public static void helloworldStatic(String msg){
            Log.d("feifei","hello world:"+msg);
        }
    
    }
    

    C++ 类 natvie-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testCallJavaMethod(JNIEnv *env, jobject instance) {
    
    
        //获取类名
        jclass  clazz = env->GetObjectClass(instance);
        if(clazz == NULL) return;
    
        jmethodID  javaMethod = env->GetMethodID(clazz,"helloworld","(Ljava/lang/String;)V");
        if(javaMethod == NULL)return;
        const char * msg = "nancy";
        jstring  jmsg = env->NewStringUTF(msg);
        env->CallVoidMethod(instance,javaMethod,jmsg);
    
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testCallStaticJavaMethod(JNIEnv *env, jobject instance) {
    
        //获取java类型
        jclass clazz = env->GetObjectClass(instance);
        if(clazz == NULL) return;
        jmethodID staticMethod = env->GetStaticMethodID(clazz,"helloworldStatic","(Ljava/lang/String;)V");
        if(staticMethod == NULL) return;
    
        jstring jmsg = env->NewStringUTF("wangfeng");
        env->CallStaticVoidMethod(clazz,staticMethod,jmsg);
    
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_getJavaObjectField(JNIEnv *env, jobject instance,
                                                                  jobject student) {
    
        jclass  clazz = env->GetObjectClass(student);
        if(clazz == NULL )return;
    
        // 获取Object 实例属性
        jfieldID  nameId = env->GetFieldID(clazz,"name","Ljava/lang/String;");
        jstring jname = (jstring)env->GetObjectField(student,nameId);
    
        jfieldID  ageId = env->GetFieldID(clazz,"age","I");
        jint jage = env->GetIntField(student,ageId);
    
        const char * name = env->GetStringUTFChars(jname,false);
        env->ReleaseStringUTFChars(jname,name);
    
    
        //获取java 类属性:
    
        jfieldID  gradeId = env->GetStaticFieldID(clazz,"grade","I");
        jint  jgrade = env->GetStaticIntField(clazz,gradeId);
    
        jfieldID  nickeNameID = env->GetStaticFieldID(clazz,"nickname","Ljava/lang/String;");
        jstring  jnickname = (jstring)env->GetStaticObjectField(clazz,nickeNameID);
    
        const char * nickeName = env->GetStringUTFChars(jnickname, false);
        env->ReleaseStringUTFChars(jnickname,nickeName);
    
        LOGD("feifei name:%s,age:%d,grade:%d,nickname:%s",name,jage,jgrade,nickeName);
    
        //JNI 设置 java对象属性
        env->SetObjectField(student,nameId,env->NewStringUTF("张三"));
        //JNI 设置 java 类属性
        env->SetStaticObjectField(clazz,nickeNameID,env->NewStringUTF("小白"));
        jstring jnameNew = (jstring)env->GetObjectField(student,nameId);
        jstring jnickNameNew = (jstring)env->GetStaticObjectField(clazz,nickeNameID);
    
        const char * newName = env->GetStringUTFChars(jnameNew, false);
        const char *newNickName = env->GetStringUTFChars(jnickNameNew, false);
    
        env->ReleaseStringUTFChars(jnameNew,newName);
        env->ReleaseStringUTFChars(jnickNameNew,newName);
        LOGD("feifei after update name:%s,age:%d,grade:%d,nickname:%s",newName,jage,jgrade,newNickName);
    
    }
    
    

    4、JNI对象的全局引用和局部引用

    Java代码的内存是由垃圾回收器来管理,而JNI代码则不受Java的垃圾回收器来管理。所以JNI代码提供了一组函数,来管理通过JNI代码生成的JNI对象,比如jobject,jclass,jstring,jarray等。

    JNI对象的局部引用

    在JNI接口函数中引用JNI对象的局部变量,都是对JNI对象的局部引用,一旦JNI接口函数返回,所有这些JNI对象都会被自动释放。
    不过我们也可以采用JNI代码提供的DeleteLocalRef函数来删除一个局部JNI对象引用。

     //声明局部变量clazz
        jclass clazz = env->GetObjectClass(instance);
    
        //手动释放 局部变量 clazz ;DeleteLocalRef 也可不用手动调用,JNI方法返回之后,会自动释放局部JNI变量
        env->DeleteLocalRef(clazz);
    

    JNI对象的全局引用

    JNI对象的全局引用分为两种,一种是强全局引用,这种引用会阻止Java的垃圾回收器回收JNI代码引用的Java对象,另一种是弱全局引用,这种全局引用则不会阻止垃圾回收器回收JNI代码引用的Java对象。

    1、强全局引用
    • NewGlobalRef用来创建强全局引用的JNI对象
    • DeleteGlobalRef用来删除强全局引用的JNI对象
    2、弱全局引用
    • NewWeakGlobalRef用来创建弱全局引用的JNI对象
    • DeleteWeakGlobalRef用来删除弱全局引用的JNI对象
    • IsSameObject用来判断两个JNI对象是否相同

    Java类 TestNatvie.java

      /**
         * 测试 JNI 强全局引用 和弱全局引用
         */
        public native void testJNIReference(Object object);
    
    

    C++ 代码 natvie-lib.cpp

    
    /**
     * (1)在JNI接口函数中引用JNI对象的局部变量,都是对JNI对象的局部引用,一旦JNI接口函数返回,所有这些JNI对象都会被自动释放。不过我们也可以采用JNI代码提供的DeleteLocalRef函数来删除一个局部JNI对象引用
     * (2)对于JNI对象,绝对不能简单的声明一个全局变量,在JNI接口函数里面给这个全局变量赋值这么简单,一定要使用JNI代码提供的管理JNI对象的函数.
     *  JNI 全局引用分为两种: 一种全局引用,这种引用会阻止Java垃圾回收器回收JNI代码引用的对象;
     *  另一种是弱全局引用,这种全局引用不会阻止垃圾回收器回收JNI 代码引用的Java对象
     *  - NewGlobalRef用来创建强全局引用的JNI对象
     *  - DeleteGlobalRef用来删除强全局引用的JNI对象
     *  - NewWeakGlobalRef用来创建弱全局引用的JNI对象
     *  - DeleteWeakGlobalRef用来删除弱全局引用的JNI对象
     *  - IsSameObject用来判断两个JNI对象是否相同
     */
    
    jobject  gThiz; //全局JNI对象引用
    jobject  gWeakThiz;//全局JNI对象弱应用
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testJNIReference(JNIEnv *env, jobject instance,jobject obj) {
    
    
        //声明局部变量clazz
        jclass clazz = env->GetObjectClass(instance);
    
        //手动释放 局部变量 clazz ;DeleteLocalRef 也可不用手动调用,JNI方法返回之后,会自动释放局部JNI变量
        env->DeleteLocalRef(clazz);
    
        //---- 强全局变量
        gThiz = env->NewGlobalRef(obj);//生成全局的JNI 对象引用,这样生成的全局的JNI对象 才可以在其他函数中使用
    
        env->DeleteGlobalRef(gThiz);//在我们不需要gThis这个全局JNI对象应用时,可以将其删除。
    
        //---- 全局弱引用
        gWeakThiz = env->NewWeakGlobalRef(obj);//生成全局的JNI对象引用,这样生成的全局的JNI对象才可以在其它函数中使用
    
        if(env->IsSameObject(gWeakThiz,NULL)){
            LOGD("全局弱引用 已经被释放了");
        }
    
        //释放 全局弱应用对象
        env->DeleteWeakGlobalRef(gWeakThiz);
    
    }
    

    5、JNI 进程间同步

    JNI可以使用Java对象进行线程同步

    • MonitorEnter函数用来锁定Java对象
    • MonitorExit函数用来释放Java对象锁

    Java类 TestNative.java

      /**
         * JNI 利用 java 对象进行线程同步
         * @param lock
         */
        public native void testJNILock(Object lock);
    
    

    C++ 类 native-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testJNILock(JNIEnv *env, jobject instance,
                                                           jobject lock) {
    
        //加锁
        env->MonitorEnter(lock);
    
        //doSomething
        LOGD("feifei, this is in lock");
    
        //释放锁
        env->MonitorExit(lock);
    
    }
    
    

    6、JNI异常相关的函数

    JNI处理Java异常

    当JNI函数调用的Java方法出现异常的时候,并不会影响JNI方法的执行,但是我们并不推荐JNI函数忽略Java方法出现的异常继续执行,这样可能会带来更多的问题。我们推荐的方法是,当JNI函数调用的Java方法出现异常的时候,JNI函数应该合理的停止执行代码。

    • ExceptionOccurred函数用来判断JNI函数调用的Java方法是否出现异常
    • ExceptionClear函数用来清除JNI函数调用的Java方法出现的异常
    /**
         *  1、env->ExceptionOccurred() 判断JNI调用java方法 是否遇到了Exception
         *  2、env->ThrowNew() JNI 可以主动抛出Java Exception异常
         */
        public native void testJavaException();
    
    

    C++ 类 native-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testJavaException(JNIEnv *env, jobject instance) {
    
        jclass  clazz = env->GetObjectClass(instance);
        if(clazz == NULL) return;
    
        jmethodID helloException_method  = env->GetMethodID(clazz,"helloException","()V");
        if(helloException_method == NULL )return;
        env->CallVoidMethod(instance,helloException_method);
        if(env->ExceptionOccurred() != NULL){
    //        env->ExceptionDescribe();
            env->ExceptionClear();
            LOGD("feifei,调用java 方法时 遇到了Exception");
            return;
    
        }
        LOGD("feifei,调用helloException 方法成功了!");
    
        LOGD("feifei,now JNI throw java exception - beging");
        jclass  expetionClazz = env->FindClass("java/lang/Exception");
        if(expetionClazz == NULL) return;
        env->ThrowNew(expetionClazz,"this is a exception");
    
    }
    

    JNI抛出Java类型的异常

    JNI通过ThrowNew函数抛出Java类型的异常

    Java类 TestNative.java

     LOGD("feifei,now JNI throw java exception - beging");
        jclass  expetionClazz = env->FindClass("java/lang/Exception");
        if(expetionClazz == NULL) return;
        env->ThrowNew(expetionClazz,"this is a exception");
    

    四 JNI 和 Java对象的互相持有

    Java对象持久化C/C++对象实例

    通常的做法是 将C++对象指针 强转为jlong 类型,保存在调用者java对象的long型变量中,一直持有。
    当需要使用该C++对象时,从Java对象中的long变量,强转化为C++对象,进而使用。

    TestNative.java

    public class TestNatvie {
        static {
            System.loadLibrary("native-lib");
        }
    
        /**
         * 用户保存 C++对象的引用
         */
        private long mNatvieId;
        /**
         * Java 对象持有 C++对象
         */
        public native void initSDK();
    
        /**
         * Java 对象释放 C++对象
         */
        public native void releasSDK();
    
    }
    
    

    native-lib.cpp

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_initSDK(JNIEnv *env, jobject instance) {
    
        Person * person = new Person();
        person->setAge(18);
        person->initSDK();
    
        jclass classzz = env->GetObjectClass(instance);
        jfieldID fid = env->GetFieldID(classzz,"mNatvieId","J");
    
        //将C++对象的地址绑定到Java变量中
        env->SetLongField(instance,fid,(jlong)person);
    
    
    }
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_releasSDK(JNIEnv *env, jobject instance) {
    
        jclass objectClass = env->GetObjectClass(instance);
        jfieldID fid = env->GetFieldID(objectClass,"mNatvieId","J");
    
        //取出java对象中保存的C++对象地址
        jlong  p = env->GetLongField(instance,fid);
    
        //转换成 C++对象
        Person * person = (Person*)p;
        person->releaseSDK();
        //释放person C++对象
        free(person);
        env->SetLongField(instance,fid,-1);
    }
    
    
    

    C/C++ 持久化Java对象

    一般做法是

    • 在本地方式中,创建一个全局引用 保存java对象:
     env->NewGlobalRef(obj);
    

    这样在其他的JNI方法中就可以任意的使用该java对象了。

    • 在不需要改java对象时,再将JNI全局引用删除即可。
    
     env->DeleteGlobalRef(gThiz);
    

    使用示例:

    TestNative.cpp

      /**
         * 利用JNI全局引用持有java 对象
         */
        public native void testJNIReference(Object object);
    
    

    natvie-lib.cpp

    jobject  gThiz; //全局JNI对象引用 - 用于持有特定的java对象。
    jobject  gWeakThiz;//全局JNI对象弱应用
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_feifei_testjni_TestNatvie_testJNIReference(JNIEnv *env, jobject instance,jobject obj) {
    
    
    
        //---- 强全局变量
        gThiz = env->NewGlobalRef(obj);//生成全局的JNI 对象引用,这样生成的全局的JNI对象 才可以在其他函数中使用
    
        env->DeleteGlobalRef(gThiz);//在我们不需要gThis这个全局JNI对象应用时,可以将其删除。
    
      
    
    }
    

    参考文章

    JNI入门教程

    JNI实例

    JNI 函数大全

    相关文章

      网友评论

          本文标题:JNI 常见用法

          本文链接:https://www.haomeiwen.com/subject/bwptfqtx.html