JNI学习记录
使用JNI_OnLoad()来初始化底层环境,并映射java层native方法
1.方法、结构体介绍
1>jint JNI_OnLoad(JavaVM* vm, void* reserved);
该方法会在Java层调用System.LoadLibrary("")时被第一个调用。因此可以在这里对底层代码进行初始化,还可以加载c层代码到Vm中;
与之对应的还有一个方法:
void JNI_OnUnload(JavaVM *jvm, void *reserved);
这个方法会在JNI库被卸载时调用,所以可以在这里对C层的资源进行释放。
2>RegisterNatives(),下面的是RegisterNatives在JNI.h中的声明与调用
//声明
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
jint);
//调用
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods){
return functions->RegisterNatives(this, clazz, methods, nMethods);
}
RegisterNatives有4个参数,第一个参数是JNIEnv指针,第二个参数是Java层中声明native方法的类,第三个参数是一个JNINativeMethod类型的指针,这个类会在下面提到,第四个参数是java层native参数的个数。
3>JNINativeMethod结构体
typedef struct {
//name是一个char类型的指针,表示的是Java层native方法名称
const char* name;
//signture是一个特殊的char指针,表示该方法的参数类型与返回值类型
const char* signature;
//fnPtr是一个void类型的指针,表示的是c/c++层对应的方法,方法前需要加上(void*进行强制转换)
void* fnPtr;
} JNINativeMethod;
下面简单介绍这个结构体怎么构造:
在我的例子里面:
//JNINativeMethod数组,用来将c层方法与java层方法进行映射
static JNINativeMethod methods[] = {
{"_sayHello", "()Ljava/lang/String;",(void *) Udp_sayHello},
{"getStringFromC", "()Ljava/lang/String;",(void *) getStringFromC},
{"getStringFromNative","()Ljava/lang/String;",(void *) getStringFromNative},
};
这里有3个的数据,分别让我Java层的3个方法与native层的3个方法进行映射,第二个方法前面的()表示的是参数,括号后面则是返回值类型,参数的写法为如下:
字符 Jni类型
* I -> jint
* F -> jfloat
* J -> jlong
* Z -> jboolean
* V -> void
* Ljava/lang/String -> jstring
要传入自定义的Java对象的化,使用L+类包名+类名 -> jobject
更多的类型可以参考这篇blog: http://blog.csdn.net/bigapple88/article/details/6756204
4>完整的代码:
- native层:
#include <stdio.h>
#include <jni.h>
//java层声明native方法的类的完整名,com.jxm.test.NDK需要写成下面的形式;
static const char *classPathName = "com/jxm/test/NDK";
//对应Java层的native方法_sayHello
static jstring Udp_sayHello(JNIEnv* env, jobject thiz) {
return env->NewStringUTF("from sayHello");
}
//对应Java层的native方法getStringFromC
static jstring getStringFromC(JNIEnv* env, jobject obj) {
return env->NewStringUTF("getStringFromC");
}
//对应Java层的native方法getStringFromNative
static jstring getStringFromNative(JNIEnv* env, jobject obj) {
return env->NewStringUTF("getStringFromNative");
}
//JNINativeMethod数组,用来将c层方法与java层方法进行映射
static JNINativeMethod methods[] = {
{"_sayHello", "()Ljava/lang/String;",(void *)Udp_sayHello},
{"getStringFromC", "()Ljava/lang/String;",(void *)getStringFromC},
{"getStringFromNative","()Ljava/lang/String;",(void *)getStringFromNative},
};
//
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
//通过className获取到jclass对象
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
//注册Native方法到JNI
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
//一个联合体,JNIEnv* 是指针指向Java环境,第二个暂时不清楚什么意思
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
//开始加载SO库
jint JNI_OnLoad(JavaVM* vm, void* reserved){
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
//判断当前JNI的版本,如果版本不为1.4,则直接返回
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
goto bail;
}
//获取JNIEnv指针
env = uenv.env;
//注册native方法
if (registerNatives(env) != JNI_TRUE) {
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
void JNI_OnUnload(JavaVM *jvm, void *reserved)
{
}
Java层代码:
package com.jxm.test; /** * Created by Administrator on 2016/10/28. */ public class NDK { public native String getStringFromC() throws IllegalStateException; public native String getStringFromNative() throws IllegalStateException; public native String _sayHello() throws IllegalStateException; }
5>小节
之前看别人分析ikjplayer的源码,发现了这种加载so库的方法,可以避免常规的,使用javah命令生成的头文件那种反人类的方法命名,可以使用自己定义的方法名。在JNI_OnLoad()时对要使用的资源进行加载也是一个值得关注的地方。