在 不使用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