1 JNI层的异常 和 JNI使用的异常
由于JNI层既涉及到Java层也涉及到C/C++层,故在JNI层,可以抛出C/C++语言定义的异常、也可以抛出java语言定义的异常,哎?等会,是不是还可以抛出JNI定义的异常?
不会,因为JNI标准没有定义自己的异常,JNI使用的都是java定义的异常,所以在JNI层只可能有C/C++定义的异常 和 java定义的异常,后面我们简称C/C++异常 和 java异常。那么在JNI层上述两种异常有哪些来源呢?请看下图:
从上图可知,在JNI层,JNI接口 或 通过JNI接口调用的java方法可以抛出java异常,C/C++代码可以抛出C/C++异常,其中通过JNI接口调用的java方法抛出的java异常,是如何从java层抛到JNI层的呢?举个例子,通过JNI定义的CallVoidMethod接口调用java方法时,若java方法抛出异常,那么CallVoidMethod方法就会在JNI层抛出这个异常,jvm已经帮我们完成了JNI层和java层抛出异常的“互联互通”工作。
2 JNI层的异常处理
(1)JNI层异常处理方法概述
我们已经知道,JNI层可以有java异常 和 C/C++异常,那如何处理JNI层的两种异常呢?
JNI层抛的C/C++异常 | 在JNI层,通过C/C++的try...catch捕获并处理 |
JNI层抛的java异常 | 在JNI层,通过JNI定义的与异常相关的接口处理 或 在java层,通过try...catch捕获并处理 |
JNI层的C/C++异常,通过在JNI层 使用C/C++的try...catch捕获并处理,JNI并没有定义处理C/C++异常的接口,JNI不能处理C/C++异常;JNI层的java异常,通过JNI定义的与异常相关的接口捕获并处理,若JNI层不予处理,在JNI方法结束时,会立即将该异常传递给java层,在java层通过对native 添加try...catch,捕获并处理JNI层抛出的java异常。
那么JNI层抛出java异常后,若不处理,抛出异常的JNI函数会立即结束吗?(即 抛出的异常会立即传给java层吗?)
不会! JNI函数将继续往下执行,直到遇到return结束 或 没遇到return,执行一段时间后自动结束(具体会执行到函数的什么位置无法确定,一般在函数正常返回之前)
下面我们说下,在JNI层处理java异常的接口 和 使用,以及如何将JNI接口抛出的异常传递给java层。
(2)涉及到的JNI接口
下面共7个接口,前6个接口用于检测、抛出、清理异常,最后1个接口用于抛出致命错误。
作用 | 接口 |
检查当前线程是否有待处理的异常 | ExceptionOccurred |
检查当前线程是否有待处理的异常 | ExceptionCheck |
将异常及堆栈的回溯信息输出到系统错误报告信道(一般是System.err) | ExceptionDescribe |
清除当前线程待处理的异常 | ExceptionClear |
在当前线程抛出 java.lang.Throwable | Throw |
在当前线程抛出 java.lang.Throwable 或 其子类的 异常对象 | ThrowNew |
抛出致命错误,并结束进程 | FatalError |
(3)接口详解
/**
* 作用:检查当前线程是否有待处理的异常
* @return 如果有 返回该异常的引用;如果没有 返回null
*
* @exceptions 不报异常
*
* 注意:只能检测到调用java代码时抛出的异常 或 调用JNI接口抛出的异常,即只能检测到jvm中抛出的异常
*/
jthrowable ExceptionOccurred();
/**
* 作用:检查当前线程是否有待处理的异常
* @return 如果有 返回JNI_TRUE;如果没有 返回JNI_FALSE
*
* @exceptions 不报异常
*
* 注意:只能检测到调用java代码时抛出的异常 或 调用JNI接口抛出的异常,即只能检测到jvm中抛出的异常
*/
jboolean ExceptionCheck();
/**
* 作用:将异常及堆栈的回溯信息输出到系统错误报告信道(一般是System.err)
*
* @exceptions 不报异常
*/
void ExceptionDescribe();
/**
* 作用:清除当前线程待处理的异常
*
* @exceptions 不报异常
*/
void ExceptionClear();
/**
* 作用:在当前线程抛出 java.lang.Throwable 异常对象JNI可以通过ExceptionOccurred获取,java层可通过try catch捕获。
* @param obj
* @return
* 0:异常抛出成功
* 负数:异常抛出失败
*
* @exceptions 要抛出的异常
*
* 注意:抛出异常后,代码将继续执行
*/
int Throw(jthrowable obj);
/**
* 作用:在当前线程抛出 java.lang.Throwable 或 其子类的 异常对象,JNI可以通过ExceptionOccurred获取,
* java层可通过try catch捕获。
* @param clazz 异常类的class实例
* @param message 异常信息
* @return
* 0:异常抛出成功
* 负数:异常抛出失败
*
* @exceptions 要抛出的异常
*
* 注意:抛出异常后,代码将继续执行
*/
jint ThrowNew(jclass clazz, const char* message);
/**
* 作用:抛出致命错误,并结束进程
* @param msg 错误信息
*
* 注意:调用后立即报错,然后退出应用程序,不论在JNI层还是java层都没有办法阻止,不会执行函数后面代码
*/
void FatalError(const char* msg);
(4)接口使用
这里介绍下,JNI层如何捕获异常和抛出异常。
/**
* 抛出异常例子,函数封装如下:
* 作用:抛出java异常
* @param env JNIEnv 实例引用
* @param execptionClassName java异常类 全类名
* @param msg 异常信息
*/
void throwExecption(JNIEnv * env, const char * execptionClassName, const char * msg){
jclass jcls = env->FindClass(nullptr == execptionClassName?"java/lang/Throwable":execptionClassName);
if (jcls != nullptr) {
//抛出指定名称的异常
env->ThrowNew(jcls, nullptr == msg? "发生异常":msg);
}
env->DeleteLocalRef(jcls);
}
//JNI层异常处理例子
void testExecption(JNIEnv * env){
//1 模拟JNI接口 或 JNI调用的java方法 抛出的异常
throwExecption(env,"java/lang/IllegalStateException","我是JNI,我这儿有异常");
//2 在每个可能抛出异常的JNI接口后面 检测 是否有异常发生
if(env->ExceptionCheck()){
//3 获取并缓存JNI层的java异常,用于后续再次抛出
jthrowable jthrowable = env->ExceptionOccurred();
//4 清除JNI层抛出的java异常
env->ExceptionClear();
//5 TODO:JNI层发生异常的处理逻辑
//6 重新抛出异常,目的是让java层也收到该异常,并通过try catch捕获并执行java层的相应逻辑
env->Throw(jthrowable);
//7 删除局部引用
env->DeleteLocalRef(jthrowable);
//8 结束方法,JNI抛出的java异常 传递 到java层
return;
}
// 抛出异常后,如果不处理,会执行该循环
for(int i = 0; i < 1000; i++){
__android_log_write(ANDROID_LOG_ERROR, "yy", std::to_string(i).c_str());
}
}
3 总结及注意
(1)在JNI层,可以有C/C++异常 和 java异常,JNI没有定义自己的异常。
(2)JNI定义的和异常相关的接口只能抛出或处理java异常。
(3)JNI接口抛出java异常后,会继续执行一段时间,然后在正常返回前自动退出,除非遇到return;java层遇到异常则不会执行后续代码,直接执行catch代码块。
(4)JNI和java可以相互捕获并处理对方抛出的java异常。
(5)FatalError接口无法被捕获,一经调用就会退出进程。
(6)JNI层处理java异常时,记得将异常再次抛出到java层,让java层也执行异常捕获逻辑