JNI 常见用法

一、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 函数大全

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容