1. JNI中获取JavaVM和 JNIEnv
JavaVM是虚拟机在JNI中的表示,一个虚拟机中只有一个JavaVM对象,这个对象是线程共享的。
JNIEnv类型是一个指向全部JNI方法的指针。该指针只在创建它的线程有效,不能跨线程传递。多线程无法共享。
使用JNI_OnLoad方法,这个方法需要自己实现。如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| jint JNI_OnLoad(JavaVM *vm,void *reserved){ LOGE("JNI_Onload in 1"); JNIEnv *env = NULL; int result = -1; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { LOGE("JNI_Onload in 2"); return JNI_ERR; } result = register_method(env);//动态注册本地方法 savedVm = vm;//保存JavaVM ,供其他地方使用。 LOGE("JNI_Onload in 3 , result is %d", result); return JNI_VERSION_1_6; }
|
这个方法是在加载相应的.so包的时候,系统主动调用的,即:
1 2 3
| static { System.loadLibrary("native-lib"); }
|
这句代码调用时,JNI_OnLoad方法就会被调用。
每一个.so文件中,只能包含一个JNI_OnLoad方法,也就是在当前.so包含的文件中,只能有一个.c文件中实现这个JNI_OnLoad方法。
如上面的做法,我们可以在头文件中声明extern JavaVM *savedVm;,然后其他文件include这个头文件后,就可以使用这个JavaVM。
很多情况下,我们需要在其他文件中的一个多线程中使用JNIEnv,比如在线程中使用FindClass方法,这个时候,我们就可以通过JavaVM对象来获取JNIEnv,如下:
1 2 3 4 5 6
| JNIEnv *env ; if (savedVm->AttachCurrentThread(&env, 0) != 0) { LOGI("Failed to attach current thread"); return; }
|
AttachCurrentThread表示当前线程与JVM进行关联,这样才能获取到JNIEnv对象;
这样我们就可以在多线程中使用JNIEnv对象,然后使用FindClass方法。当然,使用完了需要解除当前线程和虚拟机的关联,savedVm->DetachCurrentThread();,不然会报异常。
还可以在应用退出时,卸载当前虚拟机.
1
| jint DestroyJavaVM(JavaVM* vm);
|
2、动态注册JNI方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| jint JNI_OnLoad(JavaVM *vm,void *reserved){ JNIEnv *env = NULL; int result = -1; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { LOGE("JNI_Onload in 2"); return JNI_ERR; } **result = register_method(env);//动态注册本地方法** savedVm = vm;//保存JavaVM ,供其他地方使用。 LOGE("JNI_Onload in 3 , result is %d", result); return JNI_VERSION_1_6; } //这是本地方法的一个集合 JNINativeMethod methods[] = { {"helloMethod","(Ljava/lang/String;)Ljava/lang/String;",(void*)helloMethod}, {"nativeCallJava","()V",(void*)nativeCallJava}, {"nativeCallJavaInThread","()V",(void*)nativeCallJavaInThread} }; jint register_method(JNIEnv *env){ //开始注册方法 int result = registerNativeMethods(env,"com/xxxx/jni/MainActivity",methods, sizeof(methods) / sizeof(methods[0])); return result; }
jint registerNativeMethods(JNIEnv* env, const char *class_name, JNINativeMethod *methods, int num_methods) { int result = 0; //先根据类名找到类,这个类一般是用来存放所有的native方法 jclass clazz = env->FindClass(class_name); if(clazz == NULL){ return JNI_FALSE; } //调用JNI方法,注册方法 result = env->RegisterNatives(clazz, methods, num_methods); if(result < 0){ return JNI_FALSE; } return result; }
|
JNINativeMethod结构体如下:
1 2 3 4 5
| typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
|
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了Java中函数的参数和返回值
第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)
第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数>的native方法名字
关于第二个变量,表示的参数和返回值表示,可以看如下:
基本数据类型的转换
java |
native |
signature |
char |
jchar |
C |
byte |
jbyte |
B |
short |
jshort |
S |
int |
jint |
I |
long |
jlong |
L |
float |
jfloat |
F |
double |
jdouble |
D |
boolean |
jboolean |
Z |
void |
void |
V |
引用数据类型的转换
java |
native |
signature |
所有对象 |
jobject |
L+classname +; |
Class |
jclass |
Ljava/lang/Class; |
String |
jstring |
Ljava/lang/String; |
Throwable |
jthrowable |
Ljava/lang/Throwable; |
Object[] |
jobjectArray |
[L+classname +; |
byte[] |
jbyteArray |
[B |
char[] |
jcharArray |
[C |
double[] |
jdoubleArray |
[D |
float[] |
jfloatArray |
[F |
int[] |
jintArray |
[I |
short[] |
jshortArray |
[S |
long[] |
jlongArray |
[J |
boolean[] |
jbooleanArray |
[Z |
从上表可一看出,数组的JNI层数据类型需要以“Array”结尾,签名格式的开头都会有“[”。除了数组以外,其他的引用数据类型的签名格式都会以“;”结尾。
当然,我们也可以在应用退出时卸载本地方法。JNI_OnLoad方法是在动态库被加载时调用,而JNI_OnUnload则是在本地库被卸载时调用。所以这两个函数就是一个本地库最重要的两个生命周期方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int unRegisterNative(JNIEnv *env) { jclass clazz = env->FindClass(“类名”); if (clazz == NULL) { return false; } return env->UnregisterNatives(clazz) ; }
void JNI_OnUnload(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { return; } int ret = unRegisterNative(env); }
|