Android中关于JNI 的学习(六)JNI中注册方法的实现
2014-06-03 18:07
543 查看
在前面的例子中,我们会发现,当在Java类中定义一个方法的时候,如下:
public class ParamTransferTest {
public static int testval = 1;
public native void changeTestVal();
则在对应的JNI层中,由javah生成的头文件和其对应的C文件,其方法名称必须如下:
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
而方法名称之所以必须是这样,是基于在前面文章中我们提到过的一些JNI的命名规则,但是是不是一定要这样才行呢,多麻烦呀。
(tips:JNIExport 和 JNICALL是windows平台的jni编译出来的,在Android手机上,其实可以不要这两个关键字的)
答案当然是否定的。
在前面的例子中,为什么在JNI层实现的方法名必须符合一定的命名规则呢?这是因为,我们并没有提供JNI层方法和Java端方法的一个联系,或者说一个对应关系,而由于缺乏这种我们自定义的对应关系,NDK在编译的时候,操作系统在解释的时候,它们就必须制定一系列的规则,而通过这个规则去找到对应的方法。不然,如果你随便定义一个方法名,我随便定义一个方法名,别人怎么可能知道这两个就是对应的呢?无规则不成方圆,所以。。。
那么,如果我们不想再去写这些又长又臭的方法,我们就必须给出它们的对应关系,而JNI其实也提供了这样的一套机制,这篇博文就让我们来看看,如何在Android中实现这样的机制。
其实Android底层的源码中,涉及到JNI层的方法实现和加载,基本上都是通过这种注册方法的机制来实现的,包含我们上一篇文章中提到的Log的实现。
在jni.h文件中,提供了JNINativeMethod的结构,如下:
typedef struct {
const char* name;<span style="white-space:pre"> </span>//java端方法名
const char* signature;<span style="white-space:pre"> </span>//方法签名
void* fnPtr;<span style="white-space:pre"> </span>//jni层函数指针
} JNINativeMethod;
JNINativeMethod这个结构,存放的就是Java端方法跟JNI层方法的一个对应关系,其有三个字段,表示的意思,大家看注释就清楚了。
接下来,我们通过一个小Demo来看看如何在JNI中实现注册函数,然后由DVM加载使用。
1)在Java端定义一个Native方法,如下:
public class DynReg {
public native static String sayHello();
}
2)在JNI层中创建对应的C文件,这一次,我们不需要生成利用javah生成头文件之类,然后复制方法名之类的,如下:
jstring say_hello(JNIEnv *e, jobject j) {
return (*e)->NewStringUTF(e, "Say Hello from dynamic register");
}
我们并不需要遵循传统的JNI编程命名方法,我们可以自己定义我们想要的方法名称,比如say_hello等等。
4)利用JNINativeMethod结构,创建一个对应的关系,如下:
static JNINativeMethod mehtod_table[] = {
{ "sayHello", "()Ljava/lang/String;", (void*) say_hello },
};
这是一个结构数组,对应于多个方法,在这个例子中,我们只有一个方法,所以只有一个元素,我们可以看到:
4.1)“sayHello”,对应于Java端的方法
4.2)"()Ljava/lang/String",对应于其方法签名
4.3)(void*) say_hello,对应于我们上面实现的C方法
5)在JNI_OnLoad方法中将这个对应注册到DVM中,如下:
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* methods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, methods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if ((*jvm)->GetEnv(jvm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
registerNativeMethods(env, "com/lms/jni/DynReg", mehtod_table, NELEM(mehtod_table));
return JNI_VERSION_1_4;
}
简单讲一下这个过程,当这些C文件被编译成对应的so文件之后,在Java端中利用System.loadLibrary来加载so库的时候,对应类中的JNI_OnLoad方法就会被调用,这其实就是相当于一些接口的回调函数的概念, 在加载的时候,我们就可以将上面定义的JNINativeMethod数组,通过调用JNIEnv*的RegisterNatives方法,将这个数组中的方法给注册到JNIEnv中了,而在底层的实现中,当DVM调用对应的native方法的时候,会根据我们这里有没有注册到JNIEnv*中去找对应的方法,如果找到了,就直接执行,如果没有找到,JNIEnv*就会继续根据传统的JNI方法命名规范去找原先那又长又臭的方法,如果那个方法也没有找到,就会报错了。
5.1)调用registerNativeMethods,传入对应的类名“com/lms/jni/DynReg”,还有方法表method_table,方法表中方法的个数,NELEM是定义的一个宏,如下:
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
5.2)在registerNativeMethods方法中,根据类名,找出对应的类,将类,方法和方法个数调用RegisterNatives方法注册到JNIEnv*中。
6)在Android.mk文件中声明我们这个新添加的类 DynReg.c,如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := com_lms_jni_HwDemo
LOCAL_SRC_FILES := \
HwDemo.c \
JniTest.c \
ParamTransferTest.c\
DynReg.c
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
7)最后在Java端中使用这个方法,如下:
tv.setText(DynReg.sayHello());
8)结果如下:
关于在JNI中注册函数实现JNI层和Java层互相通信的方法到这里也就差不多了,利用这个方法,在编写C/C++方法的时候,是不是看起来就舒服多了啊?
结束。
public class ParamTransferTest {
public static int testval = 1;
public native void changeTestVal();
则在对应的JNI层中,由javah生成的头文件和其对应的C文件,其方法名称必须如下:
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
而方法名称之所以必须是这样,是基于在前面文章中我们提到过的一些JNI的命名规则,但是是不是一定要这样才行呢,多麻烦呀。
(tips:JNIExport 和 JNICALL是windows平台的jni编译出来的,在Android手机上,其实可以不要这两个关键字的)
答案当然是否定的。
在前面的例子中,为什么在JNI层实现的方法名必须符合一定的命名规则呢?这是因为,我们并没有提供JNI层方法和Java端方法的一个联系,或者说一个对应关系,而由于缺乏这种我们自定义的对应关系,NDK在编译的时候,操作系统在解释的时候,它们就必须制定一系列的规则,而通过这个规则去找到对应的方法。不然,如果你随便定义一个方法名,我随便定义一个方法名,别人怎么可能知道这两个就是对应的呢?无规则不成方圆,所以。。。
那么,如果我们不想再去写这些又长又臭的方法,我们就必须给出它们的对应关系,而JNI其实也提供了这样的一套机制,这篇博文就让我们来看看,如何在Android中实现这样的机制。
其实Android底层的源码中,涉及到JNI层的方法实现和加载,基本上都是通过这种注册方法的机制来实现的,包含我们上一篇文章中提到的Log的实现。
在jni.h文件中,提供了JNINativeMethod的结构,如下:
typedef struct {
const char* name;<span style="white-space:pre"> </span>//java端方法名
const char* signature;<span style="white-space:pre"> </span>//方法签名
void* fnPtr;<span style="white-space:pre"> </span>//jni层函数指针
} JNINativeMethod;
JNINativeMethod这个结构,存放的就是Java端方法跟JNI层方法的一个对应关系,其有三个字段,表示的意思,大家看注释就清楚了。
接下来,我们通过一个小Demo来看看如何在JNI中实现注册函数,然后由DVM加载使用。
1)在Java端定义一个Native方法,如下:
public class DynReg {
public native static String sayHello();
}
2)在JNI层中创建对应的C文件,这一次,我们不需要生成利用javah生成头文件之类,然后复制方法名之类的,如下:
jstring say_hello(JNIEnv *e, jobject j) {
return (*e)->NewStringUTF(e, "Say Hello from dynamic register");
}
我们并不需要遵循传统的JNI编程命名方法,我们可以自己定义我们想要的方法名称,比如say_hello等等。
4)利用JNINativeMethod结构,创建一个对应的关系,如下:
static JNINativeMethod mehtod_table[] = {
{ "sayHello", "()Ljava/lang/String;", (void*) say_hello },
};
这是一个结构数组,对应于多个方法,在这个例子中,我们只有一个方法,所以只有一个元素,我们可以看到:
4.1)“sayHello”,对应于Java端的方法
4.2)"()Ljava/lang/String",对应于其方法签名
4.3)(void*) say_hello,对应于我们上面实现的C方法
5)在JNI_OnLoad方法中将这个对应注册到DVM中,如下:
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* methods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, methods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if ((*jvm)->GetEnv(jvm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
registerNativeMethods(env, "com/lms/jni/DynReg", mehtod_table, NELEM(mehtod_table));
return JNI_VERSION_1_4;
}
简单讲一下这个过程,当这些C文件被编译成对应的so文件之后,在Java端中利用System.loadLibrary来加载so库的时候,对应类中的JNI_OnLoad方法就会被调用,这其实就是相当于一些接口的回调函数的概念, 在加载的时候,我们就可以将上面定义的JNINativeMethod数组,通过调用JNIEnv*的RegisterNatives方法,将这个数组中的方法给注册到JNIEnv中了,而在底层的实现中,当DVM调用对应的native方法的时候,会根据我们这里有没有注册到JNIEnv*中去找对应的方法,如果找到了,就直接执行,如果没有找到,JNIEnv*就会继续根据传统的JNI方法命名规范去找原先那又长又臭的方法,如果那个方法也没有找到,就会报错了。
5.1)调用registerNativeMethods,传入对应的类名“com/lms/jni/DynReg”,还有方法表method_table,方法表中方法的个数,NELEM是定义的一个宏,如下:
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
5.2)在registerNativeMethods方法中,根据类名,找出对应的类,将类,方法和方法个数调用RegisterNatives方法注册到JNIEnv*中。
6)在Android.mk文件中声明我们这个新添加的类 DynReg.c,如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := com_lms_jni_HwDemo
LOCAL_SRC_FILES := \
HwDemo.c \
JniTest.c \
ParamTransferTest.c\
DynReg.c
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
7)最后在Java端中使用这个方法,如下:
tv.setText(DynReg.sayHello());
8)结果如下:
关于在JNI中注册函数实现JNI层和Java层互相通信的方法到这里也就差不多了,利用这个方法,在编写C/C++方法的时候,是不是看起来就舒服多了啊?
结束。
相关文章推荐
- Android中关于JNI 的学习(六)JNI中注冊方法的实现
- Android中关于JNI 的学习(二)对于JNI方法名,数据类型和方法签名的一些认识
- Android中关于JNI 的学习(二)对于JNI方法名,数据类型和方法签名的一些认识
- Android JNI动态注册Native 方法(实现IDA中改名)
- Android学习之实现Canvas基本画图方法
- Android -关于注册Google Map Api Key 的方法和网页显示不全的问题
- Android JNI和NDK学习(03)--动态方式实现JNI
- android中关于jni调用java层方法的一些误导和见解
- android JNI学习之五 JNI中常用的方法
- android的HAL第一种调用hal方法中的JNI代码的实现:
- Android JNI和NDK学习(04)--NDK调试方法
- Android基于XMPP Smack Openfire下学习开发IM(一)实现用户注册、登录、修改密码和注销等
- Android JNI使用方法(“动态注册”)
- 【学习Android NDK开发】Java通过JNI调用native方法
- 在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口 (学习老罗的)
- Android学习之 Button onClickListener实现方法
- Android学习札记36:一个关于onSaveInstanceState ()方法的例子
- Android Animation学习笔记 Posted on 2010-01-11 23:00 feisky 阅读(40227) 评论(12) 编辑 收藏 关于动画的实现,Android提供了A
- 模仿android_debug_JNITest实现apk 调用framework java JNI中方法
- Android学习札记24:收集到的一些关于解决Bitmap OOM内存溢出的方法