温馨提示:基础部分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属性与方法列表如下
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);
}
返回结果
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是个一级指针。
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>
}