先编写java代码,然后编译生成.class文件
使用java的工具程序javah,如javah -o output packagename.classname 这样就会生成一个叫output的JNI层头文件(函数名有_转换后为_l)
在静态方法中native函数是如何找到的,过程如下:当java层调用native_init函数时,它会从对应的JNI库中寻找java_android_media_MediaScanner_native_linit函数,如果没有找到,就会报错,如果找到就会为native_init和java_android_media_MediaScanner_native_linit建立一个函数指针,以后再调用native时直接使用这个指针即可,这个工作是由虚拟机完成
缺点:每个class都需要使用javah生成一个头文件,并且生成的名字很长书写不便;初次调用时需要依据名字搜索对应的JNI层函数来建立关联关系,会影响运行效率
JNINativeMethod
来记录Java native函数和JNI函数的对应关系`typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;`
JNI_OnLoad函数
当我们使用System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个函数并调用该函数,因此可以在该函数中做一些初始化的动作,其实这个函数就是相当于Activity中的onCreate()方法。该函数前面有三个关键字,分别是JNIEXPORT、JNICALL和jint,其中JNIEXPORT和JNICALL是两个宏定义,用于指定该函数是JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,用于衔接Java层和JNI层,至于这些数据类型我们在后面介绍。这里的jint对应Java的int数据类型,该函数返回的int表示当前使用的JNI的版本,其实类似于Android系统的API版本一样,不同的JNI版本中定义的一些不同的JNI函数。该函数会有两个参数,其中*jvm为Java虚拟机实例,JavaVM结构体定义了以下函数:
DestroyJavaVM
AttachCurrentThread
DetachCurrentThread
GetEnv
这里我们使用了GetEnv函数获取JNIEnv变量,上面的JNI_OnLoad函数中有如下代码:
JNIEnv *env;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
这里调用了GetEnv函数获取JNIEnv结构体指针,其实JNIEnv结构体是指向一个函数表的,该函数表指向了对应的JNI函数,我们通过调用这些JNI函数实现JNI编程。
获取Java对象,完成动态注册
上面介绍了如何获取JNIEnv结构体指针,得到这个结构体指针后我们就可以调用JNIEnv中的RegisterNatives函数完成动态注册native方法了。该方法如下:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
第一个参数是Java层对应包含native方法的对象(这里就是AndroidJni对象),通过调用JNIEnv对应的函数获取class对象(FindClass函数的参数为需要获取class对象的类描述符):
jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");
第二个参数是JNINativeMethod结构体指针,这里的JNINativeMethod结构体是描述Java层native方法的,它的定义如下:
typedef struct {
const char* name;//Java层native方法的名字
const char* signature;//Java层native方法的描述符
void* fnPtr;//对应JNI函数的指针
} JNINativeMethod;
第三个参数为注册native方法的数量。一般会动态注册多个native方法,首先会定义一个JNINativeMethod数组,然后将该数组指针作为RegisterNative函数的参数传入,所以这里定义了如下的JNINativeMethod数组:
JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog}};
最后调用RegisterNative函数完成动态注册:
env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));
JNIEnv结构体
上面提到JNIEnv这个结构体,它就老厉害了,指向一个函数表,该函数表指向一系列的JNI函数,我们通过调用这些JNI函数可以实现与Java层的交互,这里简单的看看几个定义的函数:
..........
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
CallVoidMethod(jobject obj, jmethodID methodID, ...)
CallBooleanMethod(jobject obj, jmethodID methodID, ...)
..........
这里简单的看看上面的四个函数,GetFieldID()函数是获取Java对象中某个变量的ID,GetBooleanField()函数是根据变量的ID获取数据类型为Boolean的变量。GetMethodID()函数是获取Java对象中对应方法的ID,CallVoidMethod()根据methodID调用对应对象中的方法,并且该方法的返回值为Void类型。通过这些函数我们可以实现调用Java层的代码。
JNI数据类型
上面我们提到JNI定义了一些自己的数据类型。这些数据类型是衔接Java层和C/C++层的,如果有一个对象传递下来,那么对于C/C++来说是没办法识别这个对象的,同样的如果C/C++的指针对于Java层来说它也是没办法识别的,那么就需要JNI进行匹配,所以需要定义一些自己的数据类型。
1.原始数据类型
Java Type | Native Typ | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
2.引用类型
前面我们在获取AndroidJni对象的使用通过定义jclass引用,然后调用FindClass函数获取了该对象,所以JNI也定义了一些引用类型以便JNI层调用,具体的引用类型如下:
jobject (all Java objects)
|
|-- jclass (java.lang.Class objects)
|-- jstring (java.lang.String objects)
|-- jarray (array)
| |--jobjectArray (object arrays)
| |--jbooleanArray (boolean arrays)
| |--jbyteArray (byte arrays)
| |--jcharArray (char arrays)
| |--jshortArray (short arrays)
| |--jintArray (int arrays)
| |--jlongArray (long arrays)
| |--jfloatArray (float arrays)
| |--jdoubleArray (double arrays)
|
|--jthrowable
3.方法和变量的ID
?当需要调用Java中的某个方法的时候我们首先要获取它的ID,根据ID调用JNI函数获取该方法,变量的获取过程也是同样的过程,这些ID的结构体定义如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
描述符
1.类描述符
?前面为了获取Java的AndroidJni对象,是通过调用FindClass()函数获取的,该函数参数只有一个字符串参数,我们发现该字符串如下所示:
com/github/songnick/jni/AndroidJni
其实这个就是JNI定义了对类的描述符,它的规则就是将"com.github.songnick.jni.AndroidJni"中的“.”用“/”代替。
2.方法描述符
?前面我们动态注册native方法的时候结构体JNINativeMethod中含有方法描述符,就是确定native方法的参数和返回值,我们这里定义的dynamicLog()方法没有参数,返回值为空所以对应的描述符为:"()V",括号类为参数,V表示返回值为空。下面还是看看几个栗子吧:
Method Descriptor | Java Language Type |
---|---|
"()Ljava/lang/String;" | String f(); |
"(ILjava/lang/Class;)J" | long f(int i, Class c); |
"([B)V" | String(byte[] bytes); |
上面的栗子我们看到方法的返回类型和方法参数有引用类型以及boolean、int等基本数据类型,对于这些类型的描述符在下个部分介绍。这里数组的描述符以"["和对应的类型描述符来表述。对于二维数组以及三维数组则以"[["和"[[["表示:
Descriptor | Java Langauage Type |
---|---|
"[[I" | int[][] |
"[[[D" | double[][][] |
3.数据类型描述符
?前面我们说了方法的描述符,那么针对boolean、int等数据类型描述符是怎样的呢,JNI对基本数据类型的描述符定义如下:
Field Desciptor | Java Language Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | floa |
D | double |
对于引用类型描述符是以"L"开头";"结尾,示例如下所示:
Field Desciptor | Java Language Type |
---|---|
"Ljava/lang/String;" | String |
"[Ljava/lang/Object;" | Object[] |