在 不使用IDE作一次JNI开发 一文中,咱们作了一次从 Java 层到 Native 层的开发。那么,咱们能不能反过来,完成一次从 Native 层到 Java 层的开发呢?固然能,不过过程可没那么简单,而掌握 JavaVM
和 JNIEnv
这两个结构体就是关键,这两个结构体就是通往 Java 世界的大门,重要性不言而喻。html
JavaVM
这个结构体指针在简单的 JNI
开发中不多使用到,它是虚拟机的表明,从 JDK 1.2
开始,一个进程只容许建立一个虚拟机。java
当 Java 层访问 Nativce 层的时候会自动在 JNI
层建立一个 JavaVM
指针,而咱们在 JNI
层一般所使用的都是从 JavaVM
中获取的 JNIEnv
指针。那么如今咱们来看下 JavaVM
这个结构体android
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
使用起来方便些。c++
能够看到全部方法都是由结构体 JNIInvokeInterface
实现的,那么来看下这个结构体吧oracle
/* * 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
指针的。后面会举例解释这几个函数用途。ide
从 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
头文件中。post
参数解释学习
p_vm
: 是一个指向 JavaVM *
的指针,函数成功返回时会给 JavaVM *
指针赋值。p_env
: 是一个指向 JNIEnv *
的指针,函数成功返回时会给 JNIEnv *
指针赋值。vm_args
: 是一个指向 JavaVMInitArgs
的指针,是初始化虚拟机的参数。若是函数执行成功,返回 JNI_OK
(值为0),若是失败返回负值。ui
基本上能够这样理解 JNI_CreateJavaVM()
函数,它就是为了给 JavaVM *
指针 和 JNIEnv *
指针赋值。咱们获得这两个指针即可以操纵"万物",这里的"万物"指的是 Java 世界的"万物"。
那么咱们如何使用这个函数呢?这个仍是有点小复杂的,须要对 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()
函数,销毁虚拟机,
在 _JavaVM
结构体中有一个函数 getEnv()
,与之相对应的函数原型以下
jint GetEnv(JavaVM *vm, void **env, jint version);
复制代码
参数说明
JNIEnv
结构的指针的指针。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 * env
是与线程相关,所以多个线程之间不能共享同一个 env
。JNIEnv * env
,那么必须作到以下两点
JavaVM
的 AttachCurrentThread()
函数。JavaVM * mJavaVm
,那么就能够在线程中经过调用 JavaVM
的 getEnv()
函数来获取 JNIEnv * env
的值。咱们还记得 JNI_CreateJavaVM()
函数也设置 *env
的值吗?那么它确定也会执行 AttachCurrentThread()
函数把当前线程附着到虚拟机中。这也就是解释了为什么在没有明显调用 AttachCurrentThread()
的状况下,能够执行 JavaVM
的 DetachCurrentThread()
函数。
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 世界"万物"呢,后面文章会一一详述。
Oracle官网的Invocation API