上篇学习了NDK相关知识,这篇继续学习JNI相关知识,这篇文章仅作为笔记,以防以后忘记
在JNI开发中,java的数据类型并不能直接在JNI上直接使用,需要有一定的转化,比如java中的int在JNI中就是jint,下面我们来学习下数据类型
Java数据类型 | jni数据类型 | 描述 |
---|---|---|
boolean | jboolean | 无符号char类型 |
byte | jbyte | 带符号8位整形 |
char | jchar | 无符号的16位整形 |
short | jshort | 带符号的16位整形 |
int | jint | 带符号的32位整形 |
long | jlong | 带符号的64位整形 |
float | jfloat | 32位浮点型 |
java类型 | JNI类型 | 描述 |
---|---|---|
java.lang.Object | jobject | 可以表示任何java对象 |
java.lang.String | jstring | 字符串对象 |
java.lang.Class | jclass | class对象 |
Object[] | jobjectArray | java任何对象数组的表现形式 |
boolean[] | jbooleanArray | 布尔类型的数组 |
byte[] | jbyteArray | byte数组的表现形式 |
char[] | jcharArray | char数组的表现形式 |
short[] | jshortArray | short数组的表现形式 |
int[] | jintArray | int数组的表现形式 |
long[] | jlongArray | long数组的表现形式 |
float[] | jfloatArray | float数组的表现形式 |
double[] | jdoubleArray | double数组的表现形式 |
java.lang.Throwable | jthrowable | java throwable的表现形式 |
void | void | 无类型 |
其实大部分就是在java的数据类型的前面加上了小写j
来表示jni的数据类型
在JVM虚拟机中,存储数据类型名称的时候,是使用指定的描述符来使用,而不是我们使用的int,float来储存
java类型 | 类型描述符 |
---|---|
int | I |
long | J |
byte | B |
short | S |
char | C |
float | F |
double | D |
boolean | Z |
void | V |
其他引用数据类型 | L+全类名+; |
数组 | [ |
方法 | (参数)返回值 |
类型 | 描述 |
---|---|
java类型 | java.lang.String |
jni描述符 | Ljava/lang/String; |
就是L
+全类名
,其中.
换成/
,最后加上;
类型 | 描述 |
---|---|
java类型 | String[] |
jni描述符 | [Ljava/;ang/String; |
java类型 | int[][] |
jni描述符 | [[I |
类型 | 描述 |
---|---|
java类型 | long f (int n,String s,int arr[]); |
jni描述符 | (ILjava/lang/String;[I)J |
java类型 | void f (); |
jni类型 | ()V |
javaVM是java虚拟机在jni层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM
我们来看下JavaVM结构体
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
这里值分析C++版的,可以看到所有的方法都是JNIInvokeInterface
实现的,我们看下这个结构体
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
我们看到JNIInvokeInterface
结构体包含5个函数,看方法名大概能知道他们有什么作用,比如:GetEnv
函数获取JNIEnv
指针
当从Java层到Native层开发时,他会自动创建JavaVM
对象,但是当Native层到Java层开发时,需要我们主动创建JavaVM
对象,我们可以用下面的函数创建
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
第一个参数:指向JavaVM *的指针,函数调用成功会给JavaVM *赋值
第二个参数:指向JNIEnv *的指针,函数调用成功会给JNIEnv *赋值
第三个参数:是指向JavaVMInitArgs的指针,是初始化虚拟机的参数
JNIEnv是一个线程相关的结构体,该结构体代表了java在本线程的执行环境
JavaVM:JavaVM是java虚拟机在jni层的代表,全局只有一个
JNIEnv:每个线程都有一个,jni可能有多个JNIEnv
native环境创建线程,如果要访问jni,需要调用AttachCurrentThread
方法进行关联,使用DetachCurrentThread
接触关联
在_JavaVM
结构体中,有一个方法getEnv()
可以获取JNIEnv
jint GetEnv(JavaVM *vm, void **env, jint version);
第一个参数:JavaVM虚拟机对象
第二个参数:指向JNIEnv的指针
第三个参数:jni的版本,根据jdk的版本,目前有四种值,分别为 JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6
函数调用成功会给JNIEnv赋值
我们先看下JNIEnv
结构体
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
jmethodID FromReflectedMethod(jobject method)
{ return functions->FromReflectedMethod(this, method); }
jfieldID FromReflectedField(jobject field)
{ return functions->FromReflectedField(this, field); }
...省略很多函数
}
我们可以看到函数的实现最终还是交给了JNINativeInterface
去实现,我们再看下这个结构体
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
jclass (*GetSuperclass)(JNIEnv*, jclass);
jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);
jint (*Throw)(JNIEnv*, jthrowable);
jint (*ThrowNew)(JNIEnv *, jclass, const char *);
jthrowable (*ExceptionOccurred)(JNIEnv*);
void (*ExceptionDescribe)(JNIEnv*);
void (*ExceptionClear)(JNIEnv*);
void (*FatalError)(JNIEnv*, const char*);
...省略很多的方法
}
这个结构体的作用是操作java层的入口,具体有俩个作用
https://juejin.im/post/5d19bf3f6fb9a07eaa229349
https://www.jianshu.com/p/87ce6f565d37
https://blog.csdn.net/afei__/article/details/80986203