您的位置:首页 > 移动开发 > Android开发

Android JNI开发(1)--JavaVM和 JNIEnv 动态注册本地方法

2017-09-18 18:03 666 查看
这里说的JNI不是初学Android JNI时的那种,而是使用NDK相应的API进行相应的开发工作。

一、JNI中获取JavaVM和 JNIEnv

JavaVM是虚拟机在JNI中的表示,一个虚拟机中只有一个JavaVM对象,这个对象是线程共享的。

JNIEnv类型是一个指向全部JNI方法的指针。该指针只在创建它的线程有效,不能跨线程传递。多线程无法共享。

使用JNI_OnLoad方法,这个方法需要自己实现。如下

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包的时候,系统主动调用的,即:

static {
System.loadLibrary("hello");
}


这句代码调用时,JNI_OnLoad方法就会被调用。

每一个.so文件中,只能包含一个JNI_OnLoad方法,也就是在当前.so包含的文件中,只能有一个.c文件中实现这个JNI_OnLoad方法。

如上面的做法,我们可以在头文件中声明extern JavaVM *savedVm;,然后其他文件include这个头文件后,就可以使用这个JavaVM。

很多情况下,我们需要在其他文件中的一个多线程中使用JNIEnv,比如在线程中使用FindClass方法,这个时候,我们就可以通过JavaVM对象来获取JNIEnv,如下:

JNIEnv *env ;
if (savedVm->AttachCurrentThread(&env, 0) != 0)
{
LOGI("Failed to attach current thread");
return;
}


AttachCurrentThread表示当前线程与JVM进行关联,这样才能获取到JNIEnv对象;

这样我们就可以在多线程中使用JNIEnv对象,然后使用FindClass方法。当然,使用完了需要解除当前线程和虚拟机的关联,savedVm->DetachCurrentThread();,不然会报异常。

还可以在应用退出时,卸载当前虚拟机,jint DestroyJavaVM(JavaVM* vm);

二、动态注册JNI方法

动态注册JNI方法的时机也是在JNI_OnLoad方法中:

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结构体如下:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;


第一个变量name是Java中函数的名字。

第二个变量signature,用字符串是描述了Java中函数的参数和返回值

第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)

第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数的native方法名字

关于第二个变量,表示的参数和返回值表示,可以看如下:

类型签名 对应的Java类型

Z ——-> boolean

B ——-> byte

C ——-> char

S ——-> short

I ——-> int

J ——-> long

F ——-> float

D ——-> double

L全类名;——->类 比如Ljava/lang/String;

[ type ——-> type[] 比如 [B [I

(参数类型签名,…)返回值类型签名 方法类型

可以查看https://www.zybuluo.com/cxm-2016/note/563686

当然,我们也可以在应用退出时卸载本地方法。JNI_OnLoad方法是在动态库被加载时调用,而JNI_OnUnload则是在本地库被卸载时调用。所以这两个函数就是一个本地库最重要的两个生命周期方法。

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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐