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