内容简介:在当 Java 层访问 Nativce 层的时候会自动在
在 不使用IDE做一次JNI开发
一文中,我们做了一次从 Java 层到 Native 层的开发。那么,我们能不能反过来,完成一次从 Native 层到 Java 层的开发呢?当然能,不过过程可没那么简单,而掌握 JavaVM
和 JNIEnv
这两个结构体就是关键,这两个结构体就是通往 Java 世界的大门,重要性不言而喻。
JavaVM
JavaVM
这个结构体指针在简单的 JNI
开发中很少使用到,它是虚拟机的代表,从 JDK 1.2
开始,一个进程只允许创建一个虚拟机。
当 Java 层访问 Nativce 层的时候会自动在 JNI
层创建一个 JavaVM
指针,而我们在 JNI
层通常所使用的都是从 JavaVM
中获取的 JNIEnv
指针。那么现在我们来看下 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 }; 复制代码
本文分析的代码都是 C++
版本,因为 C++
版本的 JNI
使用起来方便些。
可以看到所有方法都是由结构体 JNIInvokeInterface
实现的,那么来看下这个结构体吧
/* * JNI invocation interface. */ 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*); }; 复制代码
非常简单,前三个指针作为保留使用,后五个指针为函数指针,从函数指针的名字可以推测出函数的用途,其中 DestoryJavaVM
函数是用来销毁虚拟机的, getEnv
函数是用来获取 JNIEnv
指针的。后面会举例解释这几个函数用途。
创建JavaVM
从 Java 层到 Native 层的开发的时候,我们并不需要手动创建 JavaVM
对象,因此虚拟机自动帮我们完成了这些工作。然而,如果从 Native 层到 Java 层开发的时候,我们就需要手动创建 JavaVM
对象,创建的函数原型如下
#inlcude <jni.h> jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args); 复制代码
JNI_CreateJavaVM
函数不属于任何结构体,方法声明在 jni.h
头文件中。
参数解释
-
p_vm
: 是一个指向JavaVM *
的指针,函数成功返回时会给JavaVM *
指针赋值。 -
p_env
: 是一个指向JNIEnv *
的指针,函数成功返回时会给JNIEnv *
指针赋值。 -
vm_args
: 是一个指向JavaVMInitArgs
的指针,是初始化虚拟机的参数。
如果函数执行成功,返回 JNI_OK
(值为0),如果失败返回负值。
基本上可以这样理解 JNI_CreateJavaVM()
函数,它就是为了给 JavaVM *
指针 和 JNIEnv *
指针赋值。我们得到这两个指针便可以操纵"万物",这里的"万物"指的是 Java 世界的"万物"。
使用 JavaVM
那么我们如何使用这个函数呢?这个还是有点小复杂的,需要对 Java虚拟机 有比较深的认知。那么我们只能找个例子来学习,找哪个例子呢?在 Android 源码中,Zygote 进程开启 Java 世界就是一个绝佳的例子。
Zygote 进程启动入口为 App_main.cpp
的 main()
函数
int main(int argc, char* const argv[]) { AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } 复制代码
代码是简化的,只是为了学习 JNI
而已。
main()
函数最终会调用 AppRuntime
类的 start()
函数, AppRuntime
还是定义在 App_main.cpp
文件中,它是 AndroidRuntime
的子类,并且 start()
方法是由 AndroidRuntime
类实现的。
那么,现在看下 AndroidRuntime.cpp
的 start()
函数实现
JavaVM* AndroidRuntime::mJavaVM = NULL; void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { // 1. 创建虚拟机 JNIEnv* env; if (startVm(&mJavaVM, &env, zygote) != 0) { return; } // 2. 注册函数 if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } // 3. 调用ZygoteInit.java的main()方法 env->CallStaticVoidMethod(startClass, startMeth, strArray); // 4. 从JavaVM中分离当前线程 if (mJavaVM->DetachCurrentThread() != JNI_OK) ALOGW("Warning: unable to detach main thread\n"); // 5. 销毁JavaVM if (mJavaVM->DestroyJavaVM() != 0) ALOGW("Warning: VM did not shut down cleanly\n"); } 复制代码
第一步创建虚拟机就是调用了 JNI_CreateJavaVM()
函数,函数成功返回后,就可以得到赋值的 JavaVM *
指针和 JNIEnv *
指针,也就是代码中的全局变量 mJavaVM
和 局部变量 env
。这里,需要注意下为何 mJavaVM
是全局变量,而 env
是局部变量?这个后面讲 JNIEnv
的时候会解释。
第二步,第三步就是利用获取的 JNIEnv *
来访问 Java 世界。
第四步调用 JavaVM
的 DeatchCurrentThread()
函数,这个函数从命名就可以看出是从虚拟机分离当前线程。此时,我们应该想到了 JavaVM
的另外一个函数 AttachCurrentThread()
,这个函数是把当前线程附着到虚拟机中。然而我们并没有调用附着的操作,怎么就直接出现 DeatchCurrentThread()
函数呢? 那是因为 JNI_CreateJavaVM()
直接把当前线程附着到了虚拟机中。这个在后面讲 JNIEnv
也会有解释。
第五步调用 JavaVM
的 DestroyJavaVM()
函数,销毁虚拟机,
JNIEnv
在 _JavaVM
结构体中有一个函数 getEnv()
,与之相对应的函数原型如下
jint GetEnv(JavaVM *vm, void **env, jint version); 复制代码
参数说明
- vm: 虚拟机对象。
-
env: 一个指向
JNIEnv
结构的指针的指针。 -
version: JNI版本,根据jdk的版本,目前有四种值,分别为
JNI_VERSION_1_1
,JNI_VERSION_1_2
,JNI_VERSION_1_4
,JNI_VERSION_1_6
。
这个函数执行结果有几种情况:
-
如果当前线程没有附着到虚拟机中,也就是没有调用
JavaVM
的AttachCurrentThread()
函数,那么就会设置*env
的值为NULL
,并且返回JNI_EDETACHED
(值为-2)。 -
如果参数
version
锁指定的版本不支持,那么就会设置*env
的值为NULL
,并且返回JNI_EVERSION
(值为-3)。 -
除去上面的两种异常情况,就会给
*env
设置正确的值,并且返回JNI_OK
(值为0)。
JNIEnv使用限制
函数执行结果的第一种情况来可以说明几个问题
-
JNIEnv * env
是与线程相关,因此多个线程之间不能共享同一个env
。 -
如果在Native层新建一个线程,要获取
JNIEnv * env
,那么必须做到如下两点-
线程必须调用
JavaVM
的AttachCurrentThread()
函数。 -
必须全局保存
JavaVM * mJavaVm
,那么就可以在线程中通过调用JavaVM
的getEnv()
函数来获取JNIEnv * env
的值。
-
线程必须调用
我们还记得 JNI_CreateJavaVM()
函数也设置 *env
的值吗?那么它肯定也会执行 AttachCurrentThread()
函数把当前线程附着到虚拟机中。这也就是解释了为何在没有明显调用 AttachCurrentThread()
的情况下,可以执行 JavaVM
的 DetachCurrentThread()
函数。
JNIEnv作用
GetEnv()
函数从命名可以看出是给 JNIEnv *env
赋值的,那么这个 JNIEnv
又有什么作用呢?来看下结构体声明吧
struct _JNIEnv { const struct JNINativeInterface* functions; jclass FindClass(const char* name) { return functions->FindClass(this, name); } jobject NewGlobalRef(jobject obj) { return functions->NewGlobalRef(this, obj); } jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) { return functions->GetMethodID(this, clazz, name, sig); } // ... 省略无数个函数 } 复制代码
从声明可以看出, _JNIEnv
和 _JavaVM
玩了一样的套路,函数的都是交由另外一个指针实现,而这里就是交给 JNINativeInterface
结构体指针,从结构体命名可以大致猜下意思,它应该是定义了JNI函数调用的接口,究竟是不是呢,看下结构体声明
/* * Table of interface function pointers. */ struct JNINativeInterface { void* reserved0; void* reserved1; void* reserved2; void* reserved3; jclass (*FindClass)(JNIEnv*, const char*); jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); // 调用返回值为int类型的Java方法 jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); // 获取Java对象某个变量的值 jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); // 设置Java对象某个变量的值 void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); // 创建一个Java的String对象 jstring (*NewStringUTF)(JNIEnv*, const char*); // ... 省略无数个操作Java的方法 } 复制代码
从函数的作用来看,原来这个结构体是操作 Java
层的入口,从这里就可见 JNIEnv *
指针的作用了,而这个指针正是本地函数的第一个参数,例如在 不使用IDE做一次JNI开发
一文中实现的一个本地函数如下
extern "C" JNIEXPORT jstring JNICALL Java_com_bxll_jnidemo_Hello_helloFromJNI (JNIEnv * env, jclass clazz) { const char * str_hello = "Hello from C++"; return env->NewStringUTF(str_hello); } 复制代码
注意看第一个参数 JNIEnv * env
,这是自动虚拟机自动帮我们传入的,意思就是告诉你,可以利用这个指针来操作 Java
层。
总结
通过对 _JavaVM
和 _JNIEnv
结构的了解,我们就知道利用这两个结构体指针是可以打通 Java 世界的,而具体操纵 Java 世界的是 JNIEnv *
指针,那么具体如何操作 Java 世界"万物"呢,后面文章会一一详述。
以上所述就是小编给大家介绍的《JNI: 连接Java世界的JavaVM和JNIEnv》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- TensorFlow 亚太研发负责人李双峰:TensorFlow Lite 如何连接世界?
- 获1亿元人民币A轮融资,重启世界为游戏开发者打开新世界的大门
- 建模的世界没有银弹
- 容器正在吞噬世界
- 进入docker的世界
- 翻译世界语言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
淘宝、天猫网上开店速查速用一本通
刘珂 / 北京时代华文书局 / 2015-6 / 39.8
为了帮助众多的新手卖家掌握淘宝天猫网上开店、货源准备、店铺装修、商品拍摄、交易方法、营销推广以及售后服务等知识,本书作者根据自己多年网上开店心得,并结合了多名淘宝五皇冠店主和天猫旗舰店卖家的经验,精心策划编写了本书。 《淘宝、天猫网上开店速查速用一本通:开店、装修、运营、推广完全攻略》将目前最前沿、最流行的营销理念运用到淘宝天猫网上平台,所有技术都在实际应用获得显著效果,并且还在持续创造着惊......一起来看看 《淘宝、天猫网上开店速查速用一本通》 这本书的介绍吧!