JNI异常处理
JNI函数在执行过程中会出现异常,其异常处理机制与Java和C++都不一样。JNI提供了两种检查异常的方法:
方法1 检查上一次 JNI函数调用的返回值是否为NULL。
方法2 通过调用JNI函数ExceptionOccurred()来判断是否发生异常。
检查到异常后必须予以处理。处理异常的方法也有两种:
Native方法可选择立即返回,这样异常就会在调用该Native方法的Java 代码中抛出。所以在Java代码中必须有捕获相应异常的代码,否则程序直接退出。
Native方法可以调用ExceptionClear()来清除异常,然后执行自己的异常处理代码。
JNI提供的检查和处理异常的函数如表2-5所示。
表2-5 JNI异常处理函数
注意 异常出现后,Native相关代码必须先检查清除异常,然后才能进行其他的 JNI 函数调用。当有异常未被清除时,只有以下JNI异常处理函数可被安全地调用:ExceptionOccurred()、ExceptionDescribe()?、ExceptionClear()、ExceptionDescribe()。
接下来,继续以Log系统为例讲解JNI的异常处理流程。先看以下代码:
- static jboolean android_util_Log_isLoggable(JNIEnv* env,
- jobject clazz, jstring tag, jint level)
- {
- ……
- if (tag == NULL) {//异常流程,直接退出
- return false;
- }
- jboolean result = false;
- const char* chars = env->GetStringUTFChars(tag, NULL);
- if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
- ……
- // 异常流程,释放资源,抛出异常
- env->ReleaseStringUTFChars(tag, chars);
- jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
- return false;
- } else {//正常流程
- result = isLoggable(chars, level);
- }
- env->ReleaseStringUTFChars(tag, chars);
- return result;
- }
从代码中并没有看到JNI异常处理函数的调用。我们接着分析jniThrowException方法。
该方法定义在JNIHelp.h中,并在JNIHelp.cpp中实现。代码如下:
- extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg)
- {
- JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
- if ((*env)->ExceptionCheck(e)) {//判断发生异常
- scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
- (*env)->ExceptionClear(e);//得到异常引用并清除异常
- if (exception.get() != NULL) {
- char* text = getExceptionSummary(env, exception.get());
- free(text);
- }
- }
原来JNIHelp.h中定义了jniThrowException,它只是把异常处理函数做了一个封装,方便使用而已。
2.7.2 JNI层代码和异常处理(2)
本章开头Log系统的例子就是采用函数注册的方法。这是不是意味着在应用层就不能使用这种方法?答案显然是否定的。接下来把AppJni改造成函数注册。只需要借鉴Log系统JNI注册流程,在原有的app_jni中添加如下代码即可:
- #include <string.h>
- #include <jni.h> //引入jni.h头文件。这里定义了所有JNI函数和数据类型
- //这里已经可以不用遵守JNI的函数命名规则,因为我们已经做了函数映射
- Jstring Java_com_allongriver_jni_AppJniActivity_show( JNIEnv* env,jobject thiz )
- {
- // 这部分代码不需要任何改变
- ……
- }
- //下面都是为了完成函数注册添加的代码。这里是Java层方法和JNI层方法的映射
- static JNINativeMethod gmethods[] = {
- {"show", "()Ljava/lang/String;”,
- (void*)Java_com_allongriver_jni_AppJniActivity_show},
- };
- /*
- * Register several native methods for one class.
- */
- static int registerNativeMethods(JNIEnv* env, const char* className,
- JNINativeMethod* gMethods, int numMethods)
- {
- jclass clazz;
- clazz = (*env)->FindClass(env,className);
- if (clazz == NULL) {
- return JNI_FALSE;
- }
- //调用JNIEnv提供的注册函数向虚拟机注册
- if ((*env)->RegisterNatives(env,clazz, gMethods, numMethods) < 0) {
- return JNI_FALSE;
- }
- return JNI_TRUE;
- }
- /*
- * Register native methods for all classes we know about.
- * returns JNI_TRUE on success.
- */
- static int registerNatives(JNIEnv* env)
- {
- if (!registerNativeMethods(env, "com/allongriver/jni/AppJniActivity",
- methods, sizeof(methods) / sizeof(methods[0]))) {
- return JNI_FALSE;
- }
- return JNI_TRUE;
- }
- /*虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so后
- *会首先执行这个方法,所以我们在这里做注册的动作*/
- jint JNI_OnLoad(JavaVM* vm, void* reserved)
- {
- jint result = -1;
- JNIEnv* env = NULL;
- if ( (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) ) {
- goto fail;
- }
- //最终调用(*env)->RegisterNatives,这跟Log系统是一样的
- if (registerNatives(env) != JNI_TRUE) {
- goto fail;
- }
- result = JNI_VERSION_1_4;
- fail:
- return result;
- }
至此读者可能会问,为什么同样是以函数注册方式调用JNI,在Log系统中没有执行System.loadLibrary, 也没有在JNI_OnLoad中执行注册函数呢?
那是因为系统在启动的过程中已经帮我们做了。Android启动篇会介绍这部分内容。如果应用框架层某些模块不是在系统启动过程中自动load并注册,也需要上述JNI_OnLoad步骤。读者可以参考android_media_MediaPlayer.cpp的例子。