接触jni也有很长一段时间了,项目中也会经常接触到jni的开发。每次开发总有些细节没有考虑到而导致开发中老是出一些莫名奇妙的小错误影响开发进度。今天对jni做个总结,以免今后不在犯以前犯过的错误。
jni技术在java代码的中的书写规范都是一致的。分两个步骤: 1.调用java api加载so库,例如:libtest.so 代码实现,System.loadlibrary(“test”); 2.定义native函数 例如 public native int open(int type); java部分其实是很简单需要主要的地方就是so库的名称不要写错就行,因为jvm会库名去找到库并加载到jvm指定的区域
jni技术在c/c++中的实现机制有两种: 第一种 根据包名类名匹配 例如上述的open() 方法在jni层就会对应一个native函数
jint Java_com_xx_yy_zz_open(JNIEnv* env,jobject thiz,jint type)在java层调用open方法时实际调用的是jni层的这个函数
第二种 通过RegisterNatives函数将java方法和native函数的映射注册到jvm中 例如:
static jint qt_android_open(JNIEnv* env, jobject thiz,jint type) { __android_log_print(ANDROID_LOG_INFO,"Qt", "open native "); } static JNINativeMethod methods[] = { {"open", "(I)I", (void *)qt_android_open}, }; uenv->RegisterNatives("com/xx/yy/zz/Test", methods, sizeof(methods) / sizeof(methods[0]));通过java调用open方法就会映射到qt_android_open函数 在这种jni实现的方式中有几个知识点要详细介绍下,这也是容易出错和遗忘的部分 1.methods二维数组中包含的一维数组的第二项。“(I)I” 第一项我们知道是java中的方法名,第三项是native映射的函数名,第二项实际上是指明native函数的返回值和参数列表。 以上述例子做说明 (I)I括号内的表示参数列表,括号外的表示返回值,它的类型表示在jni.h中有定义
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue;例子中表示native函数有一个整形的参数和一个整形的返回值,如果是两个参数就是(II)I 这样表示参数的顺序和类型,这只是数值性数据类型。类类型就不同了 ,见如下例子
(Ljava/lang/String;)V这种表示的是参数是String类型,类类型的表示方法 L+com/xx/yy/类名+;三部分组成。 再来个例子
(JLjava/lang/String;Z)V这表示的意思是参数jlong,jstring,jboolean
2.java 方法中static 和非static在native函数中对应的参数列表的不同
jint Java_com_xx_yy_zz_open(JNIEnv* env,jobject thiz,jint type)还是以这个为例子,如果open为static方法则native函数的地一个参数就不是jobject类型了,而是jclass类型。这个也很好理解,在java中static方法无需实例化的对象就可直接调用,而非static方法却要实例化的对象才能调用,所以jvm对两种不同方法所传的第二个参数是有区分的 。
==》jni技术中调用的第一个函数是JNI_OnLoad(JavaVM* vm, void* ),默认可以不实现这个函数,这个的调用时机是java中调用System.loadlibrary
上面已经介绍了java调用c/c++,现在我们介绍下如果在c/c++中调用java的方法 JNIEnv提供了一个注册函数ID的函数
jclass clazz1 =uenv->FindClass(className); if (clazz1 != NULL) { m_applicationClass = (jclass)uenv->NewGlobalRef(clazz1); } jmethodID mSetParamarID= uenv->GetMethodID(clazz1,"setParamar","()V");jvm会为每个注册的java方法设定一个ID,当需要调用java方式时根据这个id就能执行了
JNIEnv* env; if (g_jvm->AttachCurrentThread(&env, NULL)<0) { return; } env->CallObjectMethod(jactivity,m_mSetParamarID); g_jvm->DetachCurrentThread(); uenv->GetMethodID(clazz1,"setParamar","()V");这个函数也有()V类型的声明,这里就不在阐述了
CallVoidMethod(jobject obj, jmethodID methodID, ...) CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)c/c++调用java方法也分static和非static,两者的第一个参数也是有区别的,一个要实例化对象,一个只要类就行,从这里跟能呼应java调用c/c++时所传入的第二个参数的区别了
JNIEnv即jni的环境,这是与线程相关的,线程A与线程B的JNIEnv是不同的。
JavaVM提供以下函数
jint (*DestroyJavaVM)(JavaVM*); jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); jint (*DetachCurrentThread)(JavaVM*); jint (*GetEnv)(JavaVM*, void**, jint); jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);除此之外,基本上在jni中所能操作的函数都来源于JNIEnv,函数比较多,我在这就不一一列出 ,可以参考jni.h头文件,当中都有定义
java数据类型与native数据类型的关系,如下表
