0.JNI概述
在Android开发中会遇到使用JNI的情况,JNI是Java Native Interface的缩写,即Java本地接口,通过JNI技术可以实现两点:
1)Java程序能够调用Native函数,Native一般指的是C/C++
2)Native函数能够调用Java层的方法
1.JNI注册
JNI的注册分成两种:1)静态注册;2)动态注册
Java层实现调用Native函数的代码示例如下:
public class JniHelper
{
static {
System.loadLibrary("jni_helper");
}
private static native void nativeFunc();
}
其中jni_helper是jni层库libjni_helper.so去掉lib后的名字,是由程序运行时系统加载,带有native关键字的方法,说明此方法是native代码实现的,也就是说该方法在库libjni_helper.so中实现
所谓的注册,也就是将java层中的nativeFunc()对应到libjni_helper.so库中的实现上,将声明和实现联系起来
1)静态注册的流程一般是,先编写java层代码,将java代码编译成class文件,再使用java提供的javah工具生成一个JNI层头文件,例如:javah -o jni_helper packgename.JniHelper,packgename.JniHelper是class文件,生成一个名为jni_helper.h的头文件,其中Java层声明的nativefunc()方法在JNI头文件中会声明为
JNIEXPORT void JNICALL Java_pack_age_name_JniHelper_nativeFunc(JNIEnv*, jclass);
之所以将packagename写成pack_age_name,是因为包名中的"."会转换成"_",此处仅作示例
最后,根据这个生成的JNI层头文件,对应实现native方法就可以了,在Java层调用Native函数的时候,系统会到JNI库libjni_helper.so中去找对应的方法,找到的话就会为Java层方法和库中的方法(即JNI头文件中那一长串的方法声明)建立起关联,这就是静态注册的方式
2)实际上android中大都用的是动态注册方式,这种方式下会用到一个结构体JNINativeMethod,在JNI层代码中会定义一个JNINativeMethod数组
static JNINativeMethod gMethod[] = {
{
"nativeFunc", //Java层方法声明
"()V", //函数签名
(void *)nativeFFFunc //JNI层对应的函数指针
}
};
对应的注册函数如下:
int registerNativeMethod(JNIEnv * env, const char * className, const JNINativeMethod * gMethod, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if((*env)->RegisterNatives(env, clazz, gMethods, numMethods)<0)
{
return -1;
}
return 0;
}
调用JNIEnv的RegisterNatives函数,就能够动态注册这种Java层函数与Native函数的关联
最后还需要实现JNI_Onload函数,该函数在JNI库libjni_helper.so加载时会去调用,如果实现了该函数,则调用它,没有实现则略过,当然在静态注册的时候也可以实现该函数,完成一些初始化工作,JNI_Onload函数示例如下:
jint JNI_Onload(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if(vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK)
{
return result;
}
//动态注册JNI函数,className指明Java中的类,形如"com/example/JniHelper",包的"."由"/"代替
registerNativeMethod(JNIEnv* env, const char* className, const JNINativeMethod* gMethod, int numMethods);
return JNI_VERSION_1_4; //这个必须有,否则会报错
}
2.数据类型转换
Java数据类型分为基本数据类型和引用数据类型两种
1)基本数据类型转换如下:
Java | Native | 符号 | 位数 |
---|---|---|---|
boolean | jboolean | 无符号 | 8位 |
byte | jbyte | 无符号 | 8位 |
char | jchar | 无符号 | 16位 |
short | jshort | 有符号 | 16位 |
int | jint | 有符号 | 32位 |
long | jlong | 有符号 | 64位 |
float | jfloat | 有符号 | 32位 |
double | jdouble | 有符号 | 64位 |
2)引用数据类型转换如下:
Java | Native |
---|---|
java.lang.Class | jclass |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
其他的Object | jobject |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
3.JNI类型签名
因为Java有函数重载的概念,所以单靠函数名不能区分函数,需要有签名,签名由参数类型和返回值组成,签名格式:
(参数1类型标识参数2类型标识......参数n类型标识)返回值类型标识
类型标识表:
Java类型 | 类型标识 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
String | L/java/lang/String; |
int[] | [I |
Object[] | [L/java/lang/object; |
数组类型前面会有一个"[",引用类型最后有一个";",签名示例如下:
String f() 的签名是()Ljava/lang/String;
int f(char c, int i)的签名是(CI)I
签名写起来麻烦,看起来也别扭,不过java有一个javap工具能帮我们生成签名信息,用法如:javap -s -p classfile, classfile是编译后的class文件,-s输出内部数据类型的签名信息,-p打印所有函数和成员的签名信息