前言

本文所要介绍的异常处理是指通过JNI调用java层方法时产生的异常处理,并不是指JNI调用Native层函数时产生的异常处理,如果童鞋们想要了解Native层的异常处理可以参考笔者之前的文章《C++之异常处理》

按照java的经验,当发生异常而又没有捕获时,异常后面的代码就得不到继续执行的机会,但是在JNI中不同,在JNI中如果调用java层的方法抛出了异常,依然会继续往后执行,但是这些行为往往会带来各种各样的"惊喜"。。。因此我们需要在异常发生时将这些异常及时进行处理。

本文主要从捕获java层异常、向java层抛出异常两个方面介绍JNI中的异常处理机制。

捕获java层异常

当Native调用java层发生异常时可以通过函数​​ExceptionOccurred​​​检测是否有发生异常,通过函数​​ExceptionDescribe​​​输出异常描述信息,如果检测到异常处理完毕或者不进行异常处理又想让程序继续往下执行,那么通过函数​​ExceptionClear​​将异常清除即可。

检测是否有异常时还可以使用函数​​ExceptionCheck​​​,它与​​ExceptionOccurred​​​函数的作用类似,不同之处在于​​ExceptionCheck​​函数不会返回异常对象的引用,而是返回一个异常是否发生的jboolean标识,当返回的标识为JNI_TRUE时代表有异常发生。当调用者不关心异常的类型,仅仅关心是否发生了异常的时候,使用它会更加方便而且高效。

向java层抛出异常

当希望将捕获到的异常向java层继续抛出时,可以通过JNI函数​​ThrowNew​​抛出一个异常,这个异常可以被java层的try catch语句块捕获到。

以下是一个在Native层捕获到一个除数为0的异常,并将其抛出给java层的demo:

NumUtils.java

public class NumUtils {
public void div(int a,int b){
if(b == 0){
throw new IllegalArgumentException("除数不能为0");
}
int c = a/b;
}

// native函数
public native void callByJni();
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("jnitest");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.sample_text);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NumUtils numUtils = new NumUtils();
try {
numUtils.callByJni();
}catch (Exception e){
Log.v("NumUtils","捕获到的异常:" + e.getMessage());
}
}
});
}

}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_fly_jnitest_NumUtils_callByJni(JNIEnv *env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jmethodID mid = env->GetMethodID(clazz,"div","(II)V");
env->CallVoidMethod(thiz,mid,-1,0);
jboolean hasException = env->ExceptionCheck();
if(hasException == JNI_TRUE){
__android_log_print(ANDROID_LOG_DEBUG,"NumUtils","调用java层方法有异常");
// 清除异常,不让崩溃
env->ExceptionClear();
// 抛出异常
jclass exceptionClazz = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(exceptionClazz,"native层捕获到异常,向java层抛出");
env->DeleteLocalRef(exceptionClazz);
}
}

小结

1、为了更好地在Native层处理好异常,一般建议在调用​​CallVoidMethod​​等一系列函数之后进行异常检测。

2、在设计供Native层调用的函数时尽可能地带上返回值,方便Native层按照返回值判断是否需要处理异常等。

3、当捕获到异常需要提前return程序时需要保证各种资源及时释放。

4、为了简化抛出异常的方法《JNI编程指南》一书中给出了一个工具函数:

void JNU_ThrowByName(JNIEnv *env, const char* name, const char* msg){
jclass cls = (*env)->FindClass(env, name);
/*if cls is NULL, an exception has already been thrown */
if(cls){
(*env)->ThrowNew(env, cls, msg);
}
/* free the local ref */
(*env)->DeleteLocalRef(env, cls);
}

关注我,一起进步,人生不止coding!!!