最近一直使用NDK,jni进行开发,在使用过程中有些东西经常犯错,有些东西不太明白,此博客将会之前学习到的内容不断分享出来,有新的心得会随时更新。
tip 1:jni程序中会使用JNIEnv*和JavaVM*,但是在C和C++中两者使用的写法不同
在C中:
使用JNIEnv* env要这样 (*env)->方法名(env,参数列表)
使用JavaVM* vm要这样 (*vm)->方法名(vm,参数列表)
在C++中:
使用JNIEnv* env要这样 env->方法名(参数列表)
使用JavaVM* vm要这样 vm->方法名(参数列表)
例如同样是获取java中的String对象(key是java传过来的jstring):在C中:
const char* c_key = (*env)->GetStringUTFChars(*env,key, 0);
在C++中
const char* c_key = env->GetStringUTFChars(key, 0);
tip2: javavm初始化
当Android,JAVA端执行到System.loadLibrary()函数时,首先会去执行jni层里的JNI_OnLoad()函数,在这个函数里我们可以做几件事:
1:初始化jni版本,默认是最老的1.1版本
2:得到全局的javavm,可以通过javavm来获取jnienv等,
例如:
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
//得到一个全局的javavm
setJavaVM(vm);
//初始化,jni版本为1.4
return JNI_VERSION_1_4;
}
tip 3:通过javavm获取jnienv
一般情况下JniEnv都是有java端传过来的,但是有些情况我们无法拿到这个jniEnv,因为JniEnv是与线程相关的,如果通过java调用缓存下来使用的话容易出现问题,因此可以通过上边获取的JavaVM来得到JNIEnv
1:JAVAVM->AttachCurrentThread(env, 0)
2:JAVAVM->GetEnv((void**) env, JNI_VERSION_1_4)
当然这两个是有区别的,大家可以搜一下,这里就不赘述了
tip4:缓存共享jobject(globalRef)
在jni中经常会需要调用java的方法,那jobject对象就是必不可少的,由jni方法传递过来的jobject对象,为local refrence,在方法调用后可能被回收,因此不能被缓存使用。为了缓存使用我们需要global refrence,通过env->NewGlobalRef(obj)方法获取,之后就可以缓存使用了,当然别忘了释放env_->DeleteGlobalRef(jobject);
tip5:缓存共享jclass(globalRef)
在jni中经常会需要调用java的方法,并且可能在不同线程中都会去调用,另外可能调用次数比较多,这样的话,如果每次都是通过jclass localClass
= env->FindClass("com/test/Test");这样类似的方法去得到jclass,然后去获取jmethodid等,效率会比较低,如果只获取一次,可能报JNI ERROR (app bug): accessed stale local reference 0x5900021这个错,这个错个人理解是引用被删除了,你继续去访问使用,也就是说由于产生的jclass是local refrence调用完成后被销毁了,因此我们需要搞一个globalref出来,方法与tip4类似:
jclass localClass
= env->FindClass("com/test/Test");
globalClass=(jclass)env->NewGlobalRef(localClass);
env->DeleteLocalRef(localClass);
这样就可以了,别忘了最后用完了再合适的地方去删除global应用,见tip4
tip6:jni调用ava方法传jstring
在jni中调用java方法时,参数如果是int,float等基本变量可以直接将C里边的值传出即可但是对于类似String这种参数,不能直接用char *,而需要采用
const char *name="TESTTEST";
jstring jname=jnienvImage->NewStringUTF(name);
方式得到一个jstring,然后作为参数去调用java方法,如果采用char *作为参数,可能编译不会报错,但是运行会报类似fatal error这样的错,可能还不好排查,避免血案发生大家还是注意一下。
tip 7:java方法获取方法签名
在jni调用java方法时,需要java签名,去写这个签名时候,有时候参数比较复杂的时候,可能会经常拼错,最好的方式通过命令去生成,保证万无一失,命令如下:cmd运行到java编译文件目录下:javap -s xxxx.class即可,友情提醒,生成之后及时关闭否则工程清理时因为无法删除这个文件而引发报错