android jni中的java调c的两种方法
2016-06-25 09:03
483 查看
Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。
也就是java虚拟机通过一种机制可以找到对应的C函数
这里就涉及到静态注册和动态注册jni函数的方法
一.这里先说动态注册的方法
JNI 允许你提供一个函数映射表,注册给Jave虚拟机,这样Jvm/native就可以用函数映射表来调用相应的函数,
就可以不必通过函数名来查找需要调用的函数了,这种机制效率比较高
Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
typedef struct {
const char* name; //第一个变量name是Java中函数的名字。
const char* signature; //第二个变量signature,用字符串是描述了函数的参数和返回值
void* fnPtr; //第三个变量fnPtr是函数指针,指向C函数
} JNINativeMethod
当java通过System.loadLibrary加载完JNI动态库后,紧接着会查找一个JNI_OnLoad的函数,如果有,就调用它,
而动态注册的工作就是在这里完成的。
JNI_OnLoad()函数
JNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用,它有两个重要的作用:
指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,
例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。
初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当。
如:
//定义jni_java函数映射表
static JNINativeMethod gMethods[] = {
{"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
{"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
{"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
};
// 注册本地方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env = NULL;
jint result = -1;
// 从虚拟机中获得JNIEnv,同时指定jni版本
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)
return JNI_FALSE;
// 注册本地方法
//获取类名
jclass clazz = (*env)->FindClass(env, "com/yyj/test/classname");
if (clazz == NULL)
return JNI_FALSE;
//把获取到的类和方法一起注册到虚拟机中
if ((*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0)
return JNI_FALSE;
result = JNI_VERSION_1_4;
return result;
}
其中比较难以理解的是第二个参数,这个涉及到 JNINativeMethod::signature 描述字符串字符
下面是说明:
1)基本类型对应关系:
标识符 Jni 类型 C 类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串 Jni 类型 C 类型
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串 Java 类型 Jni 类型
L包1/包n/类名; 类名 jobject
例子:
Ljava/net/Socket; Socket jobject
4)例外(String 类):
标识串 Java 类型 Jni 类型
Ljava/lang/String; String jstring
5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串 Java 类型 Jni 类型
L包1/包n/类名$嵌套类名; 类名 jobject
本地动态库函数 不但可以传递 Java 的基本类型,还可以传递字符串,数组,以及自定义类
第二个字段:
signature:它是一种对函数返回值和参数的编码。这种编码叫做JNI字段描述符(JavaNative Interface FieldDescriptors)。
例子:
"()V" void func() JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz )
"(II)V" void func(int,int) JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jint,jint )
"(Ljava/lang/String;Ljava/lang/String;)V" void func(String,String) JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jstring )
"(Ljava/lang/String;IFJ)F" float func(String,int,float,long) JNIEXPORT jfloat JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jint,jfloat,jlong )
那我们再看看上面注册的函数怎么写
{"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
{"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
{"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
如:
/*
* Class: com_yyj_test_classname
* Method: yyjfunc
* Signature: "()Ljava/lang/String;"
*/
JNIEXPORT jstring Java_com_yyj_test_classname_yyjfunc(JNIEnv * env ,jobject thiz) //需要返回一个字符串,可以这么返回
{
char cres[512];
jstring res;
const char *upgrade_value;
memset(cres, '\0', 512);
sprintf(cres,"%s",UPFILENAME);
res = (*env)->NewStringUTF(env, cres);
return res;
}
/*
* Class: com_yyj_test_classname
* Method: yyjfunca
* Signature:"(Ljava/lang/String;)I"
JNIEXPORT jint Java_com_yyj_test_classname_yyjfunca(JNIEnv * env ,jobject thiz,jstring filepath ) //需要返回一个字符串,可以这么返回
{
const char *pfilepath;
pfilepath = (*env)->GetStringUTFChars(env,filepath,0);
return 0;
}
二.还有一种办法可以让java能调到c函数,那就是静态注册
形如:JNIEXPORT jstring JNICALL Java_com_yyj_test_classname_yyjfunc
这种方式是根据Java+packname+method来实现的,名称过长,需要JNI开始时去遍历查找,效率有点低
这种方法可以先写好java代码,然后编译,生成一个 class文件,最后通过javah来自动生成jni对应函数声明
javah -jni -classpath yourclasspath -d jni_path classname (eg. com.yyj.test.classname)
也就是java虚拟机通过一种机制可以找到对应的C函数
这里就涉及到静态注册和动态注册jni函数的方法
一.这里先说动态注册的方法
JNI 允许你提供一个函数映射表,注册给Jave虚拟机,这样Jvm/native就可以用函数映射表来调用相应的函数,
就可以不必通过函数名来查找需要调用的函数了,这种机制效率比较高
Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
typedef struct {
const char* name; //第一个变量name是Java中函数的名字。
const char* signature; //第二个变量signature,用字符串是描述了函数的参数和返回值
void* fnPtr; //第三个变量fnPtr是函数指针,指向C函数
} JNINativeMethod
当java通过System.loadLibrary加载完JNI动态库后,紧接着会查找一个JNI_OnLoad的函数,如果有,就调用它,
而动态注册的工作就是在这里完成的。
JNI_OnLoad()函数
JNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用,它有两个重要的作用:
指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,
例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。
初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当。
如:
//定义jni_java函数映射表
static JNINativeMethod gMethods[] = {
{"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
{"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
{"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
};
// 注册本地方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env = NULL;
jint result = -1;
// 从虚拟机中获得JNIEnv,同时指定jni版本
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)
return JNI_FALSE;
// 注册本地方法
//获取类名
jclass clazz = (*env)->FindClass(env, "com/yyj/test/classname");
if (clazz == NULL)
return JNI_FALSE;
//把获取到的类和方法一起注册到虚拟机中
if ((*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0)
return JNI_FALSE;
result = JNI_VERSION_1_4;
return result;
}
其中比较难以理解的是第二个参数,这个涉及到 JNINativeMethod::signature 描述字符串字符
下面是说明:
1)基本类型对应关系:
标识符 Jni 类型 C 类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串 Jni 类型 C 类型
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串 Java 类型 Jni 类型
L包1/包n/类名; 类名 jobject
例子:
Ljava/net/Socket; Socket jobject
4)例外(String 类):
标识串 Java 类型 Jni 类型
Ljava/lang/String; String jstring
5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串 Java 类型 Jni 类型
L包1/包n/类名$嵌套类名; 类名 jobject
本地动态库函数 不但可以传递 Java 的基本类型,还可以传递字符串,数组,以及自定义类
第二个字段:
signature:它是一种对函数返回值和参数的编码。这种编码叫做JNI字段描述符(JavaNative Interface FieldDescriptors)。
例子:
"()V" void func() JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz )
"(II)V" void func(int,int) JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jint,jint )
"(Ljava/lang/String;Ljava/lang/String;)V" void func(String,String) JNIEXPORT void JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jstring )
"(Ljava/lang/String;IFJ)F" float func(String,int,float,long) JNIEXPORT jfloat JNICALL java_com_yyj_test_func(JNIEnv * env,jobject thiz,jstring ,jint,jfloat,jlong )
那我们再看看上面注册的函数怎么写
{"yyjfunc", "()Ljava/lang/String;", (void *)Java_com_yyj_test_classname_yyjfunc},
{"yyjfunca", "(Ljava/lang/String;)I", (void *))Java_com_yyj_test_classname_yyjfunca},
{"yyjfuncb", "()V", (void *))Java_com_yyj_test_classname_yyjfuncb)
如:
/*
* Class: com_yyj_test_classname
* Method: yyjfunc
* Signature: "()Ljava/lang/String;"
*/
JNIEXPORT jstring Java_com_yyj_test_classname_yyjfunc(JNIEnv * env ,jobject thiz) //需要返回一个字符串,可以这么返回
{
char cres[512];
jstring res;
const char *upgrade_value;
memset(cres, '\0', 512);
sprintf(cres,"%s",UPFILENAME);
res = (*env)->NewStringUTF(env, cres);
return res;
}
/*
* Class: com_yyj_test_classname
* Method: yyjfunca
* Signature:"(Ljava/lang/String;)I"
JNIEXPORT jint Java_com_yyj_test_classname_yyjfunca(JNIEnv * env ,jobject thiz,jstring filepath ) //需要返回一个字符串,可以这么返回
{
const char *pfilepath;
pfilepath = (*env)->GetStringUTFChars(env,filepath,0);
return 0;
}
二.还有一种办法可以让java能调到c函数,那就是静态注册
形如:JNIEXPORT jstring JNICALL Java_com_yyj_test_classname_yyjfunc
这种方式是根据Java+packname+method来实现的,名称过长,需要JNI开始时去遍历查找,效率有点低
这种方法可以先写好java代码,然后编译,生成一个 class文件,最后通过javah来自动生成jni对应函数声明
javah -jni -classpath yourclasspath -d jni_path classname (eg. com.yyj.test.classname)
相关文章推荐
- android 四大组件值Service(3) 绑定式服务
- 检测字符串 字母数字中文
- Android 批量添加联系人
- Android IntentUtil跳转工具类
- 浅谈Android中的异步加载之ListView中图片的缓存及优化三
- Android中绘制2D图形基础
- "AsynchTask和Handler"的区别与用法-Android异步消息处理机制之图文代码详解
- Android的硬编码国际化
- Android下打印调试堆栈方法
- Android之 drawTextOnpath
- Android自定义Dialog动画入场
- Android support library支持包常用控件介绍(一)
- Activity中configChanges属性的用法
- ListView滑动不爽,滚动一页得滑几次?该用分页列表啦!
- Android Studio 小技巧(1):如何导入AAR文件
- Android Studio 小技巧(1):如何导入AAR文件
- android监听软键盘回车键并且解决默认点击两次的问题
- Android Studio com.android.dex.DexException: Multiple dex files define(重复引用包)
- Android之Widget
- Android启动另一个Activity时无法执行语句解决办法之加载延迟