阅读 259

Android JNI和NDK学习(二):JNIEnv 和 JavaVM

概述

上篇学习了NDK相关知识,这篇继续学习JNI相关知识,这篇文章仅作为笔记,以防以后忘记

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+全类名+;
数组 [
方法 (参数)返回值

表示一个string类

类型 描述
java类型 java.lang.String
jni描述符 Ljava/lang/String;

就是L+全类名,其中.换成/,最后加上

表示数组

类型 描述
java类型 String[]
jni描述符 [Ljava/lang/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

JNIEnv 和 JavaVM

JavaVM

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

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的作用

我们先看下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层的入口,具体有俩个作用

  • 调用java函数:使用JNIEnv调用java中的代码
  • 操作java对象:java对象传入jni层是jstring,可以使用JNIEnv来操作这个对象

参考:

juejin.im/post/5d19bf…

www.jianshu.com/p/87ce6f565…

blog.csdn.net/afei__/arti…

关注下面的标签,发现更多相似文章
评论