2.7.2 JNI层代码和异常处理

接下来看如何在JNI层实现这个Native方法。代码如下:


include<string.h>

include<jni.h>//引入jni.h头文件。这里定义了所有JNI函数和数据类型

Jstring Java_com_allongriver_jni_AppJniActivity_show(JNIEnv*env, jobject thiz)

{

/*通过JNI函数GetObjectClass得到传入对象的类信息。

这里传入的对象,就是调用Native方法的那个对象/

jclass jcls=(*env)->GetObjectClass(env, thiz);

//根据类信息得到callback方法的jmethodID

jmethodID jmId=(*env)->GetMethodID(env, jcls,"callback","()V");

//调用callback方法

(*env)->CallVoidMethod(env, thiz, jmId);

/*因为在Java层的callback中抛出了未捕获的异常,所以上面的JNI函数调用必然

*出现异常,这里必须检查并处理异常,否则异常将抛给Java层的callback方法

而此时Java层callback也没有捕获异常,此时,进程将死掉/

if((*env)->ExceptionCheck(env))

{

(*env)->ExceptionDescribe(env);

(*env)->ExceptionClear(env);//清除异常

}

//处理异常后响应Java层的调用

return(*env)->NewStringUTF(env,"Show message from JNI!");

}


如果把以上代码清除异常部分(如下所示)注释掉,看看会出现什么结果。


//(*env)->ExceptionClear(env);//清除异常


运行程序后,logcat中的日志信息如下:


D/dalvikvm(4734):Trying to load lib//加载共享库

/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0

D/dalvikvm(4734):Added shared lib

/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0

D/dalvikvm(4734):No JNI_OnLoad found in//执行共享库中的第一个方法JNI_OnLoad

/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0,skipping init

D/AppJniActivity(4734):call back from native//JNI层发现异常

W/System.err(4734):java.lang.NullPointerException

……

D/AndroidRuntime(4734):Shutting down VM

//异常被抛给Java层,Java层继续打印出异常信息

E/AndroidRuntime(4734):FATAL EXCEPTION:main//进程终止


在JNI编程中,一定要处理好JNI函数调用过程中可能出现的异常。

至此,读者可能会发现,应用层JNI编程跟应用框架层JNI编程没有多少相关性。难道JNI提供两套编程机制?

答案是肯定的。AppJni例子演示的是传统的JNI编程方式,符合JNI规范。但其缺点很明显,具体有三个:

1)需要遵守繁琐的JNI实现方法的命名规则。比如要严格遵守show函数的命名规则,如果出错,将无法调用到JNI层的实现方法:


Jstring Java_com_allongriver_jni_AppJniActivity_show(JNIEnv*env, jobject thiz)


2)如果采用应用层的JNI使用方式,就需要在框架的Java层加入System.loadLibrary("app_jni")这样的加载共享库的代码。而应用框架层会频繁调用,严重影响效率。

3)虚拟机在共享库中搜索定位JNI实现方法效率也受影响。Android应用框架层采用函数注册的方法回避这些问题。

本章开头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(JNIEnvenv, const charclassName,

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(JavaVMvm, voidreserved)

{

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的例子。