Android JNI 學習(四):JNI 介面整理 — 方法表 & Base Api & Exception Api
本文我們來總結一下JNI 提供的功能列表及相關的函式表。
注意:請注意使用術語“必須”來描述對JNI程式設計師的限制。例如,當您看到某個JNI函式必須接收非NULL物件時,您有責任確保不將NULL傳遞給該JNI函式。因此,JNI實現不需要在該JNI函式中執行NULL指標檢查。
一、介面方法表
可以通過JNIEnv引數以固定偏移量訪問每個函式。JNIEnv的型別是一個指向儲存所有JNI函式指標的結構。它的定義如下:
typedef const struct JNINativeInterface * JNIEnv;
VM初始化功能表,如下面的程式碼所示 。請注意,前三個條目是保留用於將來與COM的相容性的。NULL
在函式表的開頭附近保留了許多附加條目,因此,例如,可以在FindClass之後而不是在表的末尾新增與類相關的未來JNI操作。
const struct JNINativeInterface ... = { NULL, NULL, NULL, NULL, GetVersion, DefineClass, FindClass, FromReflectedMethod, FromReflectedField, ToReflectedMethod, GetSuperclass, IsAssignableFrom, ToReflectedField, Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe, ExceptionClear, FatalError, PushLocalFrame, PopLocalFrame, NewGlobalRef, DeleteGlobalRef, DeleteLocalRef, IsSameObject, NewLocalRef, EnsureLocalCapacity, AllocObject, NewObject, NewObjectV, NewObjectA, GetObjectClass, IsInstanceOf, GetMethodID, CallObjectMethod, CallObjectMethodV, CallObjectMethodA, CallBooleanMethod, CallBooleanMethodV, CallBooleanMethodA, CallByteMethod, CallByteMethodV, CallByteMethodA, CallCharMethod, CallCharMethodV, CallCharMethodA, CallShortMethod, CallShortMethodV, CallShortMethodA, CallIntMethod, CallIntMethodV, CallIntMethodA, CallLongMethod, CallLongMethodV, CallLongMethodA, CallFloatMethod, CallFloatMethodV, CallFloatMethodA, CallDoubleMethod, CallDoubleMethodV, CallDoubleMethodA, CallVoidMethod, CallVoidMethodV, CallVoidMethodA, CallNonvirtualObjectMethod, CallNonvirtualObjectMethodV, CallNonvirtualObjectMethodA, CallNonvirtualBooleanMethod, CallNonvirtualBooleanMethodV, CallNonvirtualBooleanMethodA, CallNonvirtualByteMethod, CallNonvirtualByteMethodV, CallNonvirtualByteMethodA, CallNonvirtualCharMethod, CallNonvirtualCharMethodV, CallNonvirtualCharMethodA, CallNonvirtualShortMethod, CallNonvirtualShortMethodV, CallNonvirtualShortMethodA, CallNonvirtualIntMethod, CallNonvirtualIntMethodV, CallNonvirtualIntMethodA, CallNonvirtualLongMethod, CallNonvirtualLongMethodV, CallNonvirtualLongMethodA, CallNonvirtualFloatMethod, CallNonvirtualFloatMethodV, CallNonvirtualFloatMethodA, CallNonvirtualDoubleMethod, CallNonvirtualDoubleMethodV, CallNonvirtualDoubleMethodA, CallNonvirtualVoidMethod, CallNonvirtualVoidMethodV, CallNonvirtualVoidMethodA, GetFieldID, GetObjectField, GetBooleanField, GetByteField, GetCharField, GetShortField, GetIntField, GetLongField, GetFloatField, GetDoubleField, SetObjectField, SetBooleanField, SetByteField, SetCharField, SetShortField, SetIntField, SetLongField, SetFloatField, SetDoubleField, GetStaticMethodID, CallStaticObjectMethod, CallStaticObjectMethodV, CallStaticObjectMethodA, CallStaticBooleanMethod, CallStaticBooleanMethodV, CallStaticBooleanMethodA, CallStaticByteMethod, CallStaticByteMethodV, CallStaticByteMethodA, CallStaticCharMethod, CallStaticCharMethodV, CallStaticCharMethodA, CallStaticShortMethod, CallStaticShortMethodV, CallStaticShortMethodA, CallStaticIntMethod, CallStaticIntMethodV, CallStaticIntMethodA, CallStaticLongMethod, CallStaticLongMethodV, CallStaticLongMethodA, CallStaticFloatMethod, CallStaticFloatMethodV, CallStaticFloatMethodA, CallStaticDoubleMethod, CallStaticDoubleMethodV, CallStaticDoubleMethodA, CallStaticVoidMethod, CallStaticVoidMethodV, CallStaticVoidMethodA, GetStaticFieldID, GetStaticObjectField, GetStaticBooleanField, GetStaticByteField, GetStaticCharField, GetStaticShortField, GetStaticIntField, GetStaticLongField, GetStaticFloatField, GetStaticDoubleField, SetStaticObjectField, SetStaticBooleanField, SetStaticByteField, SetStaticCharField, SetStaticShortField, SetStaticIntField, SetStaticLongField, SetStaticFloatField, SetStaticDoubleField, NewString, GetStringLength, GetStringChars, ReleaseStringChars, NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars, GetArrayLength, NewObjectArray, GetObjectArrayElement, SetObjectArrayElement, NewBooleanArray, NewByteArray, NewCharArray, NewShortArray, NewIntArray, NewLongArray, NewFloatArray, NewDoubleArray, GetBooleanArrayElements, GetByteArrayElements, GetCharArrayElements, GetShortArrayElements, GetIntArrayElements, GetLongArrayElements, GetFloatArrayElements, GetDoubleArrayElements, ReleaseBooleanArrayElements, ReleaseByteArrayElements, ReleaseCharArrayElements, ReleaseShortArrayElements, ReleaseIntArrayElements, ReleaseLongArrayElements, ReleaseFloatArrayElements, ReleaseDoubleArrayElements, GetBooleanArrayRegion, GetByteArrayRegion, GetCharArrayRegion, GetShortArrayRegion, GetIntArrayRegion, GetLongArrayRegion, GetFloatArrayRegion, GetDoubleArrayRegion, SetBooleanArrayRegion, SetByteArrayRegion, SetCharArrayRegion, SetShortArrayRegion, SetIntArrayRegion, SetLongArrayRegion, SetFloatArrayRegion, SetDoubleArrayRegion, RegisterNatives, UnregisterNatives, MonitorEnter, MonitorExit, GetJavaVM, GetStringRegion, GetStringUTFRegion, GetPrimitiveArrayCritical, ReleasePrimitiveArrayCritical, GetStringCritical, ReleaseStringCritical, NewWeakGlobalRef, DeleteWeakGlobalRef, ExceptionCheck, NewDirectByteBuffer, GetDirectBufferAddress, GetDirectBufferCapacity, GetObjectRefType };View Code
請注意,函式表可以在所有JNI介面指標之間共享。
二、JNI 介面基本方法(JNI Base Api)
1. GetVersion (獲取版本資訊)
jint GetVersion(JNIEnv *env);
返回JNI的版本號。
引數:
env: jni介面指標
返回值:
返回一個值,其中高位為major版本號返回,低位為minor版本號。
在 JDK/JRE 1.1中返回 0x00010001
在 JDK/JRE 1.2中返回 0x00010002
在 JDK/JRE 1.4中返回 0x00010004
在 JDK/JRE 1.6中返回 0x00010006
常量:
SINCE JDK/JRE 1.2:
#define JNI_VERSION_1_1 0x00010001 #define JNI_VERSION_1_2 0x00010002 /* Error codes */ #define JNI_EDETACHED (-2) /* thread detached from the VM */ #define JNI_EVERSION (-3) /* JNI version error
SINCE JDK/JRE 1.4:
#define JNI_VERSION_1_4 0x00010004
SINCE JDK/JRE 1.6:
#define JNI_VERSION_1_6 0x00010006
使用例項:
jint version = env->GetVersion();
2. FindClass(發現Java類)
jclass FindClass(JNIEnv *env, const char *name);
在JDK 1.1發行版中,這個函式載入本地定義的類(locally-defined class), 它搜尋在由環境變數 CLASSPATH 目錄下的子目錄和zip檔案中搜索指定的類名。
從JDK 1.2發行版之後,Java安全模型允許非本地系統類也可以載入和呼叫本地方法。FindClass 函式會使用與當前本地方法關聯的ClassLoader, 並用它來載入本地方法指定的class。如果本地方法屬於系統類(system class),則沒有ClassLoader會被呼叫。否則,將使用正確的ClassLoader來載入(load)和連結(link)指定名稱的類。
從JDK 1.2發行版之後,當通過 Invocation 介面來呼叫 FindClass 函式,將沒有當前的本地方法或與之關聯的ClassLoader。這種情況下,會使用 ClassLoader.getSystemClassLoader 來替代。這個ClassLoader 是虛擬機器用來建立應用(applications)的,它有能力定位到 java.class.path 引數下的所有類。
第二個 name 引數,使用全稱類名或陣列型別簽名(array type signature)。例如,String類的全稱類名為:
"java/lang/String"
而Object陣列型別的使用:
"[Ljava/lang/Object;"
注意:一定要注意分隔符不是 . 而是 / ,不要寫錯了。
引數:
env:JNI介面指標
name:全稱的類名(包名以 / 作為分隔符, 然後緊跟著類名),如果名字以 [開頭(陣列簽名識別符號),則返回一個數組的類,這個字串也是MUTF-8。
返回值:
指定名稱的類的物件(a class object),或者在沒有找到對應類時返回 NULL
丟擲異常:
ClassFormatError :如果class內容不是一個有效的class檔案。
ClassCircularityError:如果class或interface是它自己的父類或父介面,造成迴圈層級關係。
OutOfMemoryError:如果系統在載入的過程中記憶體不足。
NoClassDefFoundError:如果指定的類或介面沒有被找到。(當name傳null或超長時也會丟擲這個異常)
使用例項:
// Start thread which receives commands from the SA. jclass threadClass = env->FindClass("java/lang/Thread"); if (threadClass == NULL) stop("Unable to find class java/lang/Thread"); jstring threadName = env->NewStringUTF("Serviceability Agent Command Thread"); if (threadName == NULL) stop("Unable to allocate debug thread name"); jmethodID ctor = env->GetMethodID(threadClass, "<init>", "(Ljava/lang/String;)V"); if (ctor == NULL) stop("Unable to find appropriate constructor for java/lang/Thread"); // Allocate thread object jthread thr = (jthread) env->NewObject(threadClass, ctor, threadName);
以上程式碼來之 openJDK 原始碼 中的 /hotspot/agent/src/share/native/jvmdi/sa.cpp
3. GetSuperclass (獲取父類)
jclass GetSuperclass(JNIEnv *env, jclass clazz);
只要傳入的 clazz 引數不是 java/lang/Object 則返回該類的父類。
如果傳入的 clazz 引數是 java/lang/Object 則返回NULL,因為它沒有父類。當傳入的是一個介面,而不是類時,也返回 NULL 。
引數:
env:JNI介面指標
clazz: Java類物件(java class object)
返回值:
返回傳入的 clazz 的父類,或 NULL .
使用例項:
jclass clazz = env->GetObjectClass(thiz); clazz = env->GetSuperclass(clazz); jfieldID __state = env->GetFieldID(clazz, "__state", "J");
以上程式碼來至:https://github.com/liucheng98/mesos-0.22.0/blob/5cfd2c36c0e8361fa2e2d9b6f191a738d22a9cfd/src/java/jni/org_apache_mesos_state_LogState.cpp
4. IsAssignableFrom (檢查類是否能轉型)
jboolean IsAssignableFrom(JNIEnv *env, jclass class1, jclass clazz2);
檢查 clazz1 的物件是否能被安全的轉型(cast)為 clazz2
引數:
env:JNI介面指標
clazz1:第一個class引數(需要轉型的類)
clazz2:第二個class引數(轉型的目標類)
返回值:
如果是以下情況則返回 JNI_TRUE :
clazz1 和 clazz2 指向同一個java類
clazz1 是 clazz2 的子類。(向上轉型是安全的)
clazz1 是 clazz2(介面)的實現類。(也屬於向上轉型)
使用例項:
jclass listInterface = env->FindClass("java/util/List") jclass arrayListClass = env->FindClass("java/util/ArrayList") jboolean isSafe = env->IsAssignableFrom(arrayListClass, listInterface); // isSafe: true; isSafe = env->IsAssignableFrom(listInterface, arrayListClass); // isSafe: false;
三、 JNI 異常方法 (JNI Exception)
1. Throw (丟擲異常)
jint Throw(JNIEnv *env, jthrowable obj);
觸發一個 java.lang.Throwable 物件的異常被丟擲。
引數:
env:JNI介面指標
obj: java.lang.Throwable 物件
返回值:
成功則返回0, 失敗時返回賦值
丟擲異常:
丟擲 java.lang.Throwable 物件
使用例項:
jthrowable exception = env->ExceptionOccurred(); if (exception) { env->ExceptionClear(); detach_internal(env, this_obj); env->Throw(exception); return; }
以上程式碼來至 OpenJDK 原始碼 /hotspot/agent/src/os/solaris/proc/saproc.cpp
2. ThrowNew (構造一個異常物件並丟擲)
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
Exception物件的構造器函式,message為異常的錯誤訊息,clazz為異常的類。
引數:
env:JNI介面指標
clazz: java.lang.Throwable 的子類
message: 用於建立 java.lang.Throwable 物件時傳入的錯誤訊息。這個是字串是MUTF-8編碼。
返回值:
成功則返回0, 失敗時返回賦值
丟擲異常:
丟擲剛構造出來的 java.lang.Throwable 物件
使用例項:
env->ThrowNew(env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"), errMsg);
3. ExceptionOccurred (檢查是否有異常被丟擲)
jthrowable ExceptionOccurred(JNIEnv *env);
檢查是否有異常被丟擲。這個異常在本地方法呼叫 ExceptionClear() 方法或被Java程式碼處理這個異常之前都會保持在被丟擲狀態。
引數:
env:JNI介面指標
返回值:
返回過程中丟擲的異常,或沒有異常被丟擲時返回 NULL 。
使用例項:
jthrowable exception = env->ExceptionOccurred(); if (exception) { env->ExceptionClear(); detach_internal(env, this_obj); env->Throw(exception); return; }
以上程式碼來至 OpenJDK 原始碼 /hotspot/agent/src/os/solaris/proc/saproc.cpp
4. ExceptionDescribe (列印異常的stack trace)
void ExceptionDescribe(JNIEnv *env);
列印一個異常的stack trace到系統的錯誤輸出,例如 stderr 這是為了除錯提供便利。
引數:
env:JNI介面指標
使用例項:
jthrowable exc = safe_ExceptionOccurred(env); if (exc) { env->DeleteLocalRef(exc); env->ExceptionDescribe(); env->ExceptionClear(); }
以上程式碼來至:OpenJDK 原始碼中的 /jdk/src/windows/native/sun/windows/awt_Object.cpp
5. ExceptionClear (清理所有即將丟擲的異常)
void ExceptionClear(JNIEnv *env);
清理任何即將丟擲的異常。如果沒有異常被丟擲,則不起任何作用。
引數:
env:JNI介面指標
使用例項:
jthrowable exc = safe_ExceptionOccurred(env); if (exc) { env->DeleteLocalRef(exc); env->ExceptionDescribe(); env->ExceptionClear(); }
以上程式碼來至:OpenJDK 原始碼中的 /jdk/src/windows/native/sun/windows/awt_Object.cpp
6. FatalError (丟擲一個嚴重的錯誤)
void FatalError(JNIEnv *env, const char *msg);
丟擲一個嚴重錯誤,並不希望虛擬機器恢復。
引數:
env:JNI介面指標
msg : 錯誤訊息,這個字串為MUTF-8編碼
使用例項:
env->CallVoidMethod(currentThread, setThreadName, threadName) ; if( env->ExceptionCheck() ) { env->ExceptionDescribe() ; env->FatalError("setting thread name failed: could not start reading thread"); return 0L ; }
以上程式碼來至:https://github.com/tnarnold/netsnmpj/blob/3153c3e9297dd7de7db9d1a65c97f77937ae147b/netsnmpj-preliminary/native/nativeThread.cc
7. ExceptionCheck (快速檢查是否有異常被丟擲)
jboolean ExceptionCheck(JNIEnv *env);
這是一個快速函式用於檢查是否有被丟擲的異常,而不建立一個這個異常的區域性引用。
引數:
env:JNI介面指標
返回值:
有一個即將被丟擲的異常時返回 JNI_TURE ,沒有則返回 JNI_FALSE
使用例項:
env->CallVoidMethod(thread, runId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); // handle exception }
以上程式碼來至:OpenJDK 原始碼中的 /jdk/src/windows/native/sun/windows/awt_Toolkit.cpp