这节主要学习jni中的引用,C中必须告诉java什么时候你可以回收,什么时候不能够回收,所以在jni中引入了引用。 引用类型分为:局部引用和全局引用。
一.局部引用
回收规则:局部引用会在C代码执行完毕之后自动回收释放。
问题:有时候我们需要自己手动去回收释放?
场景:创建对象数组。
创建native方法:
public native void localRef();
在C中去实现该方法,我们模拟创建对象数组,并手动释放的逻辑:
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_localRef(JNIEnv *env, jobject jobj) {
// 获取一个类对象 java.lang.String
jclass cls_str = env->FindClass("java/lang/String");
// 获取构造方法(实例化)
jmethodID mid = env->GetMethodID(cls_str, "<init>", "()V");
for (int i = 0; i < 10; ++i) {
// 创建对象
jobject obj_str = env->NewObject(cls_str, mid);
// 创建数组
jobjectArray arr_obj = env->NewObjectArray(6, cls_str, obj_str);
// 中间其他逻辑,省略...
// 手动释放数组
env->DeleteLocalRef(arr_obj);
env->DeleteLocalRef(obj_str);
}
}
注:局部引用不能在多线程中传递,也不能在其他方法中调用。
二.全局引用
可以在多线程中传递,手动释放之前一直有效,也能在其他方法中调用。我们同样创建两个native方法:
public native void setGlobalRef(); // 创建全局引用
public native String getGlobalRef(); // 获取全局引用
在C中实现两个方法:
jstring global_ref = nullptr; // 全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_setGlobalRef(JNIEnv *env, jobject jobj) {
// 创建字符串
jstring str = env->NewStringUTF("Hello NDK");
if (str == nullptr) {
return;
}
// 创建一个全局引用
global_ref = (jstring)env->NewGlobalRef(str);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_getGlobalRef(JNIEnv *env, jobject jobj) {
return global_ref;
}
我们在java中调用下:
setGlobalRef();
String globalRef = getGlobalRef();
Log.e("carey", "==== globalRef:" + globalRef);
查看日志打印:
我们增加一个方法,去释放这个全局引用:
public native void releaseGlobalRef();
并实现这个方法:
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_releaseGlobalRef(JNIEnv *env, jobject thiz) {
env->DeleteGlobalRef(global_ref);
}
我们获取引用后,进行释放,睡眠1s后再次获取这个引用,看看打印结果:
String globalRef = getGlobalRef();
Log.e("carey", "==== globalRef:" + globalRef);
releaseGlobalRef();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String globalRefEnd = getGlobalRef();
Log.e("carey", "==== globalRef释放后:" + globalRefEnd);
我们能够看到,这个全局引用被释放了。
三.异常处理
我们声明一个native方法:
public native int optionException();
在方法实现里,我们调用一个不存在的方法:
extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_optionException(JNIEnv *env, jobject thiz) {
// 获取java中的类
jclass ndk_obj = env->GetObjectClass(thiz);
// 获取方法对象
jmethodID mid = env->GetMethodID(ndk_obj, "getIntRandom1", "(I)I");
// 执行方法
jint result_int = env->CallIntMethod(thiz, mid, 100);
return result_int;
}
调用方法后会崩溃,方法找不到,打印日志如下:
我们改造上面的方法进行异常捕获处理:
extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_optionException(JNIEnv *env, jobject thiz) {
// 获取java中的类
jclass ndk_obj = env->GetObjectClass(thiz);
// 获取方法对象
jmethodID mid = env->GetMethodID(ndk_obj, "getIntRandom1", "(I)I");
// 处理异常,检测发生了异常,进行处理
jthrowable exception = env->ExceptionOccurred();
if (exception) {
// 清空异常,否则java程序无法执行
env->ExceptionClear();
// 处理异常,调用存在的方法
mid = env->GetMethodID(ndk_obj, "getRandomInt", "(I)I");
}
// 执行方法
jint result_int = env->CallIntMethod(thiz, mid, 100);
return result_int;
}
上面代码我们进行了异常捕获处理,同时调用了正确存在的方法,并且返回这个结果,我们在java中进行调用打印:
int result = optionException();
Log.e("carey", "==== 处理异常result:" + result);
查看打印:
我们也能看到程序不会崩溃,并正常运行。
我们看看怎么在java中去catch C中异常,先声明native方法,并实现:
public native int catchException();
extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_catchException(JNIEnv *env, jobject thiz) {
int a = 100;
int b = 200;
if (a != b) {
jclass cls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(cls, "IllegalArgumentException!");
return -1;
}
return 0;
}
这里主动去抛出一个异常,在java中去捕获,并打印:
try {
int result = catchException();
Log.e("carey", "==== 异常捕获result:" + result);
} catch (Exception e) {
Log.e("carey", "==== 异常捕获e:" + e.getMessage());
}
查看打印:
四.总结
今天主要介绍了局部和全局引用,还有异常的捕获处理。喜欢的点赞收藏,感谢!