JNI核心知识梳理

本文详细介绍了JNI接口注册、数据类型转换、访问Java成员,以及C/C++与Java的交互技巧,特别关注C++与C的差异,包括JNIEnv使用和externC的必要性。涵盖了本地方法调用、异常处理和缓存策略等内容。

温馨提示:基础部分1~3是以Java、C为示例程序梳理JNI核心知识,C++与C不同之处参考[四、JNI中C与C++的差异性](# 四、JNI中C与C++的差异性)

一、jni接口注册方式

jobject与jclass区别

每个naive函数,都至少有两个参数(JNIEnv*,jclass或者jobject)

1,当native方法为静态方法时:

jclass代表native方法所属类的class对象

2,当native方法为非静态方法时

jobject代表native方法所属的对象

平台特性:

1,mac支持中文编码UTF-8。window支持中文编码GB2312

2,mac编译jni为.dylib,windows编译jni为.obj,linux编译jni为.so

1.1、静态注册

java部分示例

public class JNIDemo {
    static {
        /**
         * 静态加载jni库(另外还有动态加载方式)
         * 这里jni_impl对应lib/libjni_impl.dylib("lib"+"jni_impl"+".dylib")
         */
        System.loadLibrary("jni_impl");
      	//也可以加载全路径
      	//System.load("/Users/zhanglei/Desktop/jni例子/jni_impl/build/release/libjni_impl.dylib");
    }

    //定义naive方法,返回String类型
    public native String getStringFromC();

    //定义静态native方法,返回String类型
    public native static String getStringFromC2();

    public static void main(String[] args) {
        //打印测试
        String jobjectStr=new JNIDemo().getStringFromC();
        String jclassStr=getStringFromC2();
        System.out.println(jobjectStr+"\n"+jclassStr);
    }
}
//打印结果
//jobject string from c
//jclass string from c

c部分示例

#include "./jni/com_hs_demo_JNIDemo.h"
#include "jni.h"
#include <stdio.h>

JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_getStringFromC (JNIEnv *env, jobject obj) {
    //jobject是
    return (*env)->NewStringUTF(env,"jobject string from c");
}

JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_getStringFromC2 (JNIEnv *env, jclass clazz) {
    return (*env)->NewStringUTF(env,"jclass string from c");
}

1.2、动态注册

当静态注册、动态注册同时存在,动态注册会覆盖静态注册

java部分示例

public class JNIDemo {
    public static void main(String[] args) {
        System.out.println(getStringFromC());
    }

    public native static String getStringFromC();

    static {
        System.loadLibrary("jni_impl");
    }
}
//打印结果
//Hello C String

c部分示例

#include "./jni/com_hs_demo_JNIDemo.h"
#include "jni.h"
#include <stdio.h>

jstring getStringFromC (JNIEnv* env, jclass jcls) {
    //env二级指针
    //代表Java运行环境,调用Java中的代码
    return (*env)->NewStringUTF(env, "Hello C String");
}

jint RegisterNatives(JNIEnv* env) {
    jclass cls = (*env)->FindClass(env, "com/hs/demo/JNIDemo");
    if (cls == NULL){
        return JNI_ERR;
    }

    // 结构体数组
    JNINativeMethod jniNativeMethod[] = {
            {"getStringFromC", "()Ljava/lang/String;", (void*)getStringFromC}
    };
    //结构体数组的长度,就代表有多少个方法需要注册
    //其他元素的大小都跟0号元素大小一致
    jint nMethods = sizeof(jniNativeMethod) / sizeof(jniNativeMethod[0]);
    return (*env)->RegisterNatives(env, cls, jniNativeMethod, nMethods);
}

//动态库加载时,自动执行
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    // 首先获取JNIEnv
    JNIEnv* env = NULL;
    if ((*vm)->GetEnv(vm, &env, JNI_VERSION_1_6) != JNI_OK){
        return JNI_ERR;
    }
    // 动态注册方法
    if (RegisterNatives(env) != JNI_OK) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

注意:动态注册类似于java的反射,所不同的是java的反射比较损耗性能,c/c++的动态注册性能接近直接查找,没有性能损耗。

二、jni数据类型

java类型 -> jni类型 -> c类型

2.1、基本数据类型

java基本数据类型与jni数据类型映射关系

boolean jboolean
byte 		jbyte
char 		jchar
short 	jshort
int 		jint
long 		jlong
float 	jfloat
double 	jdouble
void 		void

2.2、引用数据类型

String		jstring
object 		jobject
byte[]		jByteArray		//数组,基本数据类型的数组
object[] 	jobjectArray	//对象数组,包括String[]

2.3、jni类型 -> c类型参考jni.h

三、c/c++访问java的成员

java调用c的参考之前的文章

3.1、访问属性

案例:通过c修改java的属性字符串变量演示这个过程。步骤:

3.1.1、通过jobject获取jclass

GetObjectClass c方法传入jobject获取jclass

jclass cls=(*env)->GetObjectClass(env,jobj);

3.1.2、获取java属性

GetFieldID字段:JNIEnv、jclass、属性名称、属性签名

jfieldID fid=(*env)->GetFieldID(env,cls,"key","Ljava/lang/String;");

java属性与方法列表如下

Java属性与方法签名列表

3.1.3、将java的字符串转为c字符指针

char *c_str=(*env)->GetStringUTFChars(env,jstr,JNI_FALSE);

3.1.4、在c里面实现字符串拼接

char *c_str=(*env)->GetStringUTFChars(env,jstr,JNI_FALSE);
char text[20]="super ";
strcat(text,c_str);

补充:使用GetStringUTFChars会创建对象,用完之后要释放

(*env)->ReleaseStringUTFChars(env,jstr,c_str);

3.1.5、将c的字符串转化为jni的字符串jstring

jstring new_jstr=(*env)->NewStringUTF(env,text);

3.1.6、将jni的字符串set到java的类中

(*env)->SetObjectField(env,jobj,fid,new_jstr);

3.1.7、全部代码

c部分代码实现

JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_accessFieId(JNIEnv *env, jobject jobj) {
    //step1:通过jobject获取jclass (类似java反射)
    jclass cls = (*env)->GetObjectClass(env, jobj);
    /**
     * step2:获取java属性
     * GetFieldID字段:JNIEnv、jclass、属性名称、属性签名
     */
    jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    if (fid) { printf("fild is not null"); }
    else {
        printf("fild is null");
    }
    /**
     * step3:获取key属性的值
     */
    jstring jstr = (*env)->GetObjectField(env, jobj, fid);
    /**
     * step4:jstring -> c的字符串
     * isCopy
     */
    //jboolean指针
    const char *c_str = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
    //意义:isCopy为JNI_FASLE,c_str和jstr都是指向同一个字符串,不能修改java字符串

    //拼接得到新的字符串
    char text[20] = "super ";
    strcat(text, c_str);

    //step5:c字符串 -> jstring
    jstring new_jstr = (*env)->NewStringUTF(env, text);

    //step6:修改key
    (*env)->SetObjectField(env, jobj, fid, new_jstr);

    //只要使用了GetStringUTFChars,一定要释放
    (*env)->ReleaseStringUTFChars(env, jstr, c_str);

    return new_jstr;
}

java部分代码实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public String key = "javaTest";

    public native String accessFieId();

    public static void main(String[] args) {
        //打印测试
        JNIDemo jniDemo = new JNIDemo();
        System.out.println("修改前" + jniDemo.key);
        jniDemo.accessFieId();
        System.out.println("修改后" + jniDemo.key);
    }
}
//运行结果
//修改前javaTest
//修改后super javaTest
//fild is not null

3.2、访问静态属性

java实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public static int count = 1;

    public native void accessStaticFieId();

    public static void main(String[] args) {
        //打印测试
        JNIDemo jniDemo = new JNIDemo();
        //静态属性
        System.out.println("修改前" + jniDemo.count);
        jniDemo.accessStaticFieId();
        System.out.println("修改后" + jniDemo.count);
    }
}
//打印结果:
// 修改前1
// 修改后2

c部分实现

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_accessStaticFieId(JNIEnv *env, jobject jobj) {    //jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //jfieldID
    jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
    //GetStatic<Type>Field
    jint count = (*env)->GetStaticIntField(env, cls, fid);
    count++;
    //修改
    // SetStatic<Type>Field
    (*env)->SetStaticIntField(env, cls, fid, count);
}

3.3、访问方法

java实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void accessMethod();

    public int genRandomInt(int num) {
        return num + 1;
    }

    public static void main(String[] args) {        //打印测试
        JNIDemo jniDemo = new JNIDemo();
        jniDemo.accessMethod();
    }
}
//random num:201

c实现

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_accessMethod(JNIEnv *env, jobject jobj) {
    //jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //jmethodID
    jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
    //调用
    // Call<Type>Method
    jint random = (*env)->CallIntMethod(env, jobj, mid, 200);
    printf("random num:%ld", random);
}

3.4、访问静态方法

java部分实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void accessStaticMethod();

    public static String getUUID() {
        return "UUID123213213";
    }

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        jniDemo.accessStaticMethod();
    }
}

c部分实现(最终会在电脑指定路径下创建一个文件)

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_accessStaticMethod(JNIEnv *env, jobject jobj) {
    //jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //jmethodID
    jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
    //调用
    // CallStatic<Type>Method
    jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);
    //随机文件名称 uuid.txt
    // jstring -> char*
    // isCopy出参(jboolean指针类型) 代表java和c操作的是同一个字符串,如果函数内部复制了则会改变isCopy的值
    jboolean isCopy = NULL;
    char *uuid_str = (*env)->GetStringUTFChars(env, uuid, &isCopy);
    //拼接
    char filename[100];
    sprintf(filename, "D://%s.txt", uuid_str);
    sprintf(filename, "/Users/zhanglei/Desktop/%s.txt", uuid_str);
    FILE *fp = fopen(filename, "w");
    fputs("这是文本", fp);
    fclose(fp);
    //只要使用了GetStringUTFChars,一定要释放
    (*env)->ReleaseStringUTFChars(env,uuid,uuid_str);
}

3.5、访问构造方法

重要性:可以实例化一个对象,使用java的api

案例:使用java.util.Date产生一个当前的时间戳

3.5.1、获取Date类

jclass cls = (*env)->FindClass(env, "java/util/Date");

3.5.2、根据类找到构造函数

jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");

使用GetMethodID来获取构造函数,GetMethodID共四个入参:

jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);

c第三个name方法名参数:构造函数写死"",非构造函数需要查看java方法签名,命令为

javap -s -p java.util.Date

注意:这里的javap -s -p命令后面接的是编译后的.class文件地址,而不是.java文件

第四个sig签名参数:

根据命令找到为"()V"

3.5.3、实例化一个Date对象

jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);

3.5.4、调用getTime方法

首先仍然使用GetMethodID获取getTime方法

jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");

然后调用getTime方法。

注意:这里调用需要返回值,使用jni的CallLongMethod方法,需要什么返回值调用对应函数,比如

jlong time = (*env)->CallLongMethod(env, date_obj, mid);

然后打印即可得到java的当前时间

printf("\ntime:%lld\n",time);

3.5.5、全部代码

c部分实现

JNIEXPORT jobject JNICALL Java_com_hs_demo_JNIDemo_accessConstructor(JNIEnv *env, jobject jobj) {
    jclass cls = (*env)->FindClass(env, "java/util/Date");
    //jmethodID
    jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    //实例化一个Date对象
    jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
    //调用getTime方法
    jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
    jlong time = (*env)->CallLongMethod(env, date_obj, mid);
    printf("\ntime:%lld\n", time);
    return date_obj;
}
//打印结果//time:1628428607391

java部分实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native Date accessConstructor();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        jniDemo.accessConstructor();
    }
}

3.6、调用父类的方法

c语言通过虚函数结合FindClass父类可以子类对象调用到父类的方法

3.6.1、java部分

新建两个父子类Human与Man

public class Human {
    public void sayHi() {
        System.out.println("人打招呼...");
    }
}
public class Man extends Human {
    @Override
    public void sayHi() {
        super.sayHi();
        System.out.println("男人打招呼...");
    }
}

然后新建一个Man对象和调用父类的native方法accessNonvirtualMethod

java的封装、继承、多态。

1.封装就是利用修饰符去限制能够访问的对象的属性或方法,常见的是用private修饰属性,通过public暴露出去,如javabean

2.继承就是子类可以单继承父类的属性或方法。可以重写子类继承父类的方法,但是要注意修饰符(子类要大于等于父类);父类与子类返回值同有无(子类返回值要小于等于父类);参数类型要保持一致。正常来说显示继承父类的属性不用加super,但是一旦子类和父类属性重名了,用this指示子类,super指示父类。子类由于继承了父类所以在堆中存有父类的属性和方法,但是只有new出来的才作为实例对象存在堆中

3.多态就是为了调用重写子类重写的方法的一个功能(前提是父类的引用和子类继承父类方法的重写)。正常来说想要调用一个类的属性或方法需要新建这个类的实例对象,但是如果类很多的话,每次用时都新建就会很麻烦,多态将子类的对象赋值给了父类的引用,所以可以直接使用【父类】.【子类方法】,某种程度上很好的解决了这个麻烦

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    // java的封装、继承、多态。
    public Human human = new Man();

    public native void accessNonvirtualMethod();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        // java多态决定这里调用Man的sayHi方法
        jniDemo.human.sayHi();
        jniDemo.accessNonvirtualMethod();
    }
}
//人打招呼...
//男人打招呼...
//人打招呼...
//男人打招呼...
//人打招呼...

3.6.2、c部分实现

这里GetObjectClass是根据对象查找当前的类,相当于java的human.getClass()

FindClass是没有类查找当前对象,相当于java的Class.forName(“”),效率要低一点

GetFieldID方法最后一个参数是对象的签名,可以通过javap -s -p

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_accessNonvirtualMethod(JNIEnv *env, jobject jobj) {
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //获取man属性(对象)
    jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/hs/demo/Human;");
    //获取
    jobject human_obj = (*env)->GetObjectField(env, jobj, fid);
    //执行sayHi方法
    jclass human_cls = (*env)->FindClass(env, "com/hs/demo/Human");
    //注意:传父类的名称    
    jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V");
    //执行
    (*env)->CallObjectMethod(env, human_obj, mid);
    // 调用首个父类的方法
    (*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);
}

返回结果

//人打招呼…
//男人打招呼…
//人打招呼…

java可以直接Class.forName(“”)找到父类,感觉没啥用。但是基础技术还是需要沉淀

3.7、android JNI C代码中文返回乱码

问题:返回jni的数组到java中乱码。jni语言中的NewStringUTF是utf-16编码,在windows下需要转成支持中文的支持中文的gb2312

解决问题思路1:在jni返回字符传的时候,使用c库转码成支持gb2312,参考android JNI C代码中文返回乱码

解决问题思路2:在jni返回字符传的时候,使用java的转码,这个直接可用的String库

String(byte bytes[], String charsetName)

java部分代码

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native String chineseChars(String in);

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        System.out.println(jniDemo.chineseChars("宋喆"));
    }
}
//返回结果
// 马蓉与宋江

c部分代码

JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_chineseChars(JNIEnv *env, jobject jobj, jstring in) {
    //输出
//    char *c_str = (*env)->GetStringUTFChars(env, in, JNI_FALSE);
//    printf("%s\n", c_str);
    //c -> jstring
     char *c_str = "马蓉与宋江";
    // char c_str[] = "马蓉与宋喆";
    // jstring jstr = (*env)->NewStringUTF(env, c_str);
    //执行String(byte bytes[], String charsetName)构造方法需要的条件
    // 1.jmethodID
    // 2.byte数组
    // 3.字符编码jstring
    jclass str_cls = (*env)->FindClass(env, "java/lang/String");
    jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");
    //jbyte -> char
    // jbyteArray -> char[]
    jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
    //byte数组赋值
    // 0->strlen(c_str),从头到尾
    // 对等于,从c_str这个字符数组,复制到bytes这个字符数组
    (*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
    //字符编码jstring,macos是UTF-8,windows用GB2312,也可通过改终端字符集处理
    jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
    //调用构造函数,返回编码之后的jstring
    return (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName);
}

其中java.lang.String的签名

javap -s -p java.lang.String

3.8、jni字符串加密

案例:通过ASCII码偏移这种方式隐藏明码,简单的实现字符串加密的过程,当然你也可以换成自定义的加密算法。

Java 部分

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    //加密native方法
    public native String encrypt(String str);

    //解密native方法
    public native String decrypt(String str);

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        String encrypt = jniDemo.encrypt("宋喆");
        System.out.println("加密后的结果:" + encrypt);
        String decrypt = jniDemo.decrypt(encrypt);
        System.out.println("加密后的结果:" + decrypt);
    }
}
//运行结果
// 加密后的结果:괒ퟐ
// 加密后的结果:宋喆

c部分

//加密
JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_encrypt(JNIEnv *env, jobject jobj, jstring str) {
    char *c_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
    int count = strlen(c_str);
    char cryptograph[128] = {'\0'};
    for (int i = 0; i < count; i++) { cryptograph[i] = c_str[i] + i + 5; }
    cryptograph[count] = '\0';
    return (*env)->NewStringUTF(env, cryptograph);
}

//解密JNIEXPORT
jstring JNICALL Java_com_hs_demo_JNIDemo_decrypt(JNIEnv *env, jobject jobj, jstring str) {
    char *c_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
    int count = strlen(c_str);
    char cryptograph[128] = {'\0'};
    for (int i = 0; i < count; i++) { cryptograph[i] = c_str[i] - i - 5; }
    cryptograph[count] = '\0';
    return (*env)->NewStringUTF(env, cryptograph);
}

另外,商用版参考

3.9、数组set

java数组通过jni同步到c之后,可以在c里面修改后,内存同步到java区域

java部分实现

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void giveArray(int[] array);

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        int[] array = new int[]{9, 10, 37, 2, 8, 1};
        //排序
        jniDemo.giveArray(array);
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
}
//返回结果
// 1
// 2
// 8
// 9
// 10
// 37
// 0x5bd910,0x5bd908

c部分实现

int compare(int *a, int *b) {
    return (*a) - (*b);
}

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_giveArray(JNIEnv *env, jobject jobj, jintArray arr) {
    //jintArray -> jint指针 ->c int数组
    jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
    printf("%#x,%#x\n", &elems, &arr);
    //数组的长度
    int len = (*env)->GetArrayLength(env, arr);
    //排序
    qsort(elems, len, sizeof(jint), compare);
    //同步
		//mode
		//0,Java数组进行更新,并且释放C/C++数组
		//JNI_ABORT,Java数组不进行更新,但是释放C/C++数组
		//JNI_COMMIT,Java数组进行更新,不释放C/C+数组(函数执行完,数组还是会释放)
    (*env)->ReleaseIntArrayElements(env, arr, elems, 0);
}

3.10、数组get

同理从jni返回数组到java也存在数据同步问题,因为GetIntArrayElements实际上是拷贝了一份地址已经改变

java部分代码

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native int[] getArray(int len);

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        int[] array2 = jniDemo.getArray(3);
        System.out.println("===================");
        for (int i : array2) {
            System.out.println(i);
        }
    }
}

C部分代码实现

JNIEXPORT jintArray JNICALL Java_com_hs_demo_JNIDemo_getArray(JNIEnv *env, jobject jobj, jint len) {
    //创建一个指定大小的数组
    jintArray jin_arr = (*env)->NewIntArray(env, len);
    jint *elems = (*env)->GetIntArrayElements(env, jin_arr, NULL);
    int i = 0;
    for (; i < len; i++) { elems[i] = i; }
    //同步
		//mode
		//0,Java数组进行更新,并且释放C/C++数组
		//JNI_ABORT,Java数组不进行更新,但是释放C/C++数组
		//JNI_COMMIT,Java数组进行更新,不释放C/C+数组(函数执行完,数组还是会释放)
    (*env)->ReleaseIntArrayElements(env, jin_arr, elems, 0);
    return jin_arr;
}

返回结果:

0
1
2

最后一句代码如果不同步,返回的结果是

0
0
0

3.11、jni引用

在java中把变量赋值为null可以出发gc回收,但在jni中对象的指针回收需要删除引用。

在jni中对象分为局部引用与全局引用。

3.11.1、局部引用

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void localRef();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        jniDemo.localRef();
    }
}
//局部引用
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_localRef(JNIEnv *env, jobject jobj) {
    for (int i = 0; i < 5; ++i) {
        //创建Date对象
        jclass cls = (*env)->FindClass(env, "java/util/Date");
        jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
        jobject obj = (*env)->NewObject(env, cls, constructor_mid);
        //不再使用object对象        //通知垃圾回收期回收这些对象
        (*env)->DeleteLocalRef(env, obj);
    }
}

3.11.2、全局引用

全局引用的好处:共享(跨方法、还可以跨线程)、手动控制内存使用

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void createGlobalRef();

    public native String getGlobalRef();

    public native void deleteGlobalRef();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        System.out.println("创建全局引用");
        jniDemo.createGlobalRef();
        System.out.println("获取引用数据:" + jniDemo.getGlobalRef());
        System.out.println("删除全局引用");
        jniDemo.deleteGlobalRef();
        System.out.println("删除之后再获取引用数据:" + jniDemo.getGlobalRef());
    }
}
jstring global_str;
//创建
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_createGlobalRef(JNIEnv *env, jobject jobj) {
    jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!");
    global_str = (*env)->NewGlobalRef(env, obj);
}

//获得JNIEXPORT
jstring JNICALL Java_com_hs_demo_JNIDemo_getGlobalRef(JNIEnv *env, jobject jobj) { return global_str; }
//释放
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_deleteGlobalRef(JNIEnv *env, jobject jobj) {
    (*env)->DeleteGlobalRef(env, global_str);
}

返回结果

image-20210810143336694

3.11.2、弱全局引用

弱引用作用:节省内存,在内存不足时可以释放所引用的对象。可以引用一个不常用的对象

使用NewWeakGlobalRef创建,使用DeleteWeakGlobalRef删除

用法与全局引用一样,但是不保证引用的对象被回收

软引用、弱引用、虚引用-他们的特点及应用场景

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public native void createGlobalRef();

    public native String getGlobalRef();

    public native void deleteGlobalRef();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();

        jniDemo.createGlobalRef();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.gc();
            System.out.println(jniDemo.getGlobalRef());
            jniDemo.deleteGlobalRef();
        }).start();
    }
}
//null
//注释掉System.gc()打印jni development is powerful!
jstring global_str;
//创建
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_createGlobalRef(JNIEnv *env, jobject jobj) {
    jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!");
    global_str = (*env)->NewWeakGlobalRef(env, obj);
}

//获得JNIEXPORT
jstring JNICALL Java_com_hs_demo_JNIDemo_getGlobalRef(JNIEnv *env, jobject jobj) { return global_str; }
//释放
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_deleteGlobalRef(JNIEnv *env, jobject jobj) {
    (*env)->DeleteGlobalRef(env, global_str);
}

3.12、异常处理

jni自己抛出的是Throwable异常

用户通过throwNew抛出的异常,可以在java层捕捉

3.12.1、在java中捕获异常

我们定义exception的native方法

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    //这个key在3.12.3抛出异常中使用
    public String key = "asdf";

    public native void exception();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        try {
            jniDemo.exception();
        } catch (Exception e) {
            System.out.println("发生异常");
        }
        System.out.println("发生异常之后");
    }
}
//打印结果
// 发生异常
// 发生异常之后

在jni中抛出一个NoSuchFieldException

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_exception(JNIEnv *env, jobject jobj){
    jclass cls = (*env)->GetObjectClass(env, jobj);
    jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");
    //检测是否发生Java异常
    jthrowable exception = (*env)->ExceptionOccurred(env);
    if (exception != NULL){
        //让Java代码可以继续运行
        //清空异常信息
        (*env)->ExceptionClear(env);

        //补救措施
        fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    }

    //获取属性的值
    jstring jstr = (*env)->GetObjectField(env, jobj, fid);
    char *str = (*env)->GetStringUTFChars(env, jstr, NULL);

    //对比属性值是否合法
    if (strcmp(str, "super jason") != 0){
        //认为抛出异常,给Java层处理
        jclass newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
        (*env)->ThrowNew(env,newExcCls,"key's value is invalid!");
    }
}

3.12.2、在jni中捕获异常

我们使用ExceptionOccurred来获取异常,使用ExceptionClear来清除异常

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_exception (JNIEnv *env, jobject jobj) {
    jclass clz=(*env)->GetObjectClass(env,jobj);
    jfieldID fid=(*env)->GetFieldID(env,clz,"key2","Ljava/lang/String;");
    jthrowable exception=(*env)->ExceptionOccurred(env);
    if (exception!=NULL){
        //让java代码可以继续运行
        //清空异常信息
        (*env)->ExceptionClear(env);
    }
}

结果

3.12.3、抛出异常

jni中可以通过ThrowNew方法将jni异常抛给java层去处理,jni抛出的异常没办法用在java层try catch掉

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }

    public String key="asdf1";

    public native void exception();

    public static void main(String[] args) {
        JNIDemo jniDemo=new JNIDemo();

        try{
            jniDemo.exception();
        }catch (Exception e){
            System.out.println("发生异常:"+e.toString());
        }
        System.out.println("发生异常之后");
    }
}
//打印结果
//发生异常:java.lang.IllegalArgumentException: keys value is invalid
//发生异常之后
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_exception (JNIEnv *env, jobject jobj) {
    jclass clz=(*env)->GetObjectClass(env,jobj);
    jfieldID fid=(*env)->GetFieldID(env,clz,"key","Ljava/lang/String;");

    jstring jstr=(*env)->GetObjectField(env,jobj,fid);
    char *str=(*env)->GetStringUTFChars(env,jstr,NULL);

    //对比属性值是否合法
    if (strcmp(str,"asdf")!=0){
        //认为抛出异常,给java层处理
        jclass newExcCls=(*env)->FindClass(env,"java/lang/IllegalArgumentException");
        (*env)->ThrowNew(env,newExcCls,"keys value is invalid");
    }
}

3.13、缓存策略

3.13.1、局部的静态变量

局部的静态变量作用域仅限于这个方法,但是生命周期会持续到程序退出。

应用场景:多次调用jni方法,对里面的变量进行缓存。全局静态变量也可以实现这个效果,但是变量可以被其他方法访问了

public class JNIDemo {
    static {
        System.loadLibrary("jni_impl");
    }
    public String key = "jason";

    public native void cached();

    public static void main(String[] args) {
        JNIDemo jniDemo = new JNIDemo();
        for (int i = 0; i < 5; i++) {
            jniDemo.cached();
        }
    }
}
//打印结果
// ===================GetFieldID
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_cached
        (JNIEnv* env, jobject jobj) {
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //获取jfieldID只获取一次
    //局部静态变量
    static jfieldID key_id = NULL;
    if (key_id == NULL){
        key_id = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
        printf("--------GetFieldID-------\n");
    }
}

3.13.2、初始化全局变量

动态库加载完成之后立刻缓存起来

public class JNIDemo {
    public String key = "adf";

    static {
        System.loadLibrary("jni_impl");
    }
    //产生指定范围的随机数
    public int genRandomInt(int max) {
        System.out.println("genRandomInt");
        return new Random().nextInt(max);
    }
    public static native void myStaticMethod();
    public native void initIds();

    public static void main(String[] args) {
        JNIDemo jniDemo=new JNIDemo();
        jniDemo.initIds();
    }
}
public class Test {
    public static void main(String[] args) {
      //静态native方法可以没有实例调用
        JNIDemo.myStaticMethod();
    }
}
//初始化全局变量,动态库加载完成之后,立刻缓存起来
jfieldID key_fid;
jmethodID random_mid;
JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_initIds
        (JNIEnv* env, jobject jobj) {
    jclass cls = (*env)->GetObjectClass(env, jobj);
    key_fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    random_mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
}

JNIEXPORT void JNICALL Java_com_hs_demo_JNIDemo_myStaticMethod
        (JNIEnv* env, jclass jcls) {
    printf("%#x\n", key_fid);
}

四、JNI中C与C++的差异性

这是C语言版

JNIEXPORT jstring JNICALL Java_com_hs_demo_JNIDemo_getStringFromC (JNIEnv *env, jobject obj) {
    //jobject是
    return (*env)->NewStringUTF(env,"jobject string from c");
}

这是C++版

extern "C"
JNIEXPORT jstring JNICALL
Java_com_hs_androidcmakedemo_MainActivity_GetHell(JNIEnv *env, jobject thiz) {
    // TODO: implement GetHell()
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

4.1、JNIEnv在c与c++中的源码分析

注意这里用法上,C中的JNIEnv是个二级指针,C++的JNIEnv是个一级指针。

详细参考JNIEnv在c与c++中的源码分析

4.2、extern “C”

C++相比于C,会在函数前面加关键字extern “C” ,分析:

1、C不支持函数的重载,编译之后函数名不变;

2、C++支持函数的重载(这点与Java一致),编译之后函数名会改变;

解决方法是定义函数时在前面加上extern "C"修饰,告诉编译器这函数要被C调用(当然,其实是JNI),使用gcc编译,要保留源码中的函数名

他们之间的关系如下图所示

语言函数重载编译器产物
C不支持gcc动态库.so、静态库.a
C++支持g++动态库.so、静态库.a

总结:

1、jni 可以调用本地C函数。
2、jni 调用C++库时,首先要将C++库提供的功能封装成纯C格式的函数接口,然后jni里面调用这些C接口。

一个是 jni调用c。另一个是jni调用c,c调用c++。

另外在c++中引入c头文件时,也需要用**extern “C”**包裹

extern "C" {
#include <string>
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星雨在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值