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

概述

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

JNI的数据类型和类型描述符

在JNI开发中,java的数据类型并不能直接在JNI上直接使用,需要有一定的转化,比如java中的int在JNI中就是jint,下面我们来学习下数据类型

基本数据类型

Java数据类型jni数据类型描述
booleanjboolean无符号char类型
bytejbyte带符号8位整形
charjchar无符号的16位整形
shortjshort带符号的16位整形
intjint带符号的32位整形
longjlong带符号的64位整形
floatjfloat32位浮点型

引用数据类型

java类型JNI类型描述
java.lang.Objectjobject可以表示任何java对象
java.lang.Stringjstring字符串对象
java.lang.Classjclassclass对象
Object[]jobjectArrayjava任何对象数组的表现形式
boolean[]jbooleanArray布尔类型的数组
byte[]jbyteArraybyte数组的表现形式
char[]jcharArraychar数组的表现形式
short[]jshortArrayshort数组的表现形式
int[]jintArrayint数组的表现形式
long[]jlongArraylong数组的表现形式
float[]jfloatArrayfloat数组的表现形式
double[]jdoubleArraydouble数组的表现形式
java.lang.Throwablejthrowablejava throwable的表现形式
voidvoid无类型

其实大部分就是在java的数据类型的前面加上了小写j来表示jni的数据类型

类型描述符

在JVM虚拟机中,存储数据类型名称的时候,是使用指定的描述符来使用,而不是我们使用的int,float来储存

java类型类型描述符
intI
longJ
byteB
shortS
charC
floatF
doubleD
booleanZ
voidV
其他引用数据类型L+全类名+;
数组[
方法(参数)返回值
表示一个string类
类型描述
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

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来操作这个对象

参考:

https://juejin.im/post/5d19bf3f6fb9a07eaa229349

https://www.jianshu.com/p/87ce6f565d37

https://blog.csdn.net/afei__/article/details/80986203

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值