Android NDK——必知必會之JNI的C++操作函式詳解和小結(三)
引言
上一篇講解了一些關於JNI和NDK的必知必會的理論知識和機制,由於篇幅問題把關於JNI的重要的函式放到這篇,具體使用留到下一篇,此係列文章基連結:
一、JNI中的函式概述
在JNI層我們基本上都是通過env指標來呼叫jni.h標頭檔案裡定義的函式,JNI的函式語法與Java 有些異同,建議對比著學習,效果會更好。
二、操作物件的相關函式
函式名稱(env->函式名) | 功能說明 |
---|---|
jobject AllocObject( jclass clazz) | 不通過jclass對應類的構造方法並返回其引用或者null |
jobject NewObject(jclass clazz, jmethodID |
通過反射構造方法建立物件,通過不定引數傳入對應的實參 |
jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue *args) | 通過反射構造方法建立物件,通過陣列接收傳入對應的實參 |
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) | 通過反射構造方法建立物件,通過va_list傳入對應的實參 |
jclass GetObjectClass( jobject obj); | 反射獲取obj對應的jclass 位元組碼物件 |
jclass GetSuperclass(jclass clazz) | 獲取父類或者說超類 。 如果 clazz 代表類class而非類 object,則該函式返回由 clazz 所指定的類的超類。 如果 clazz指定類 object 或代表某個介面,則該函式返回NULL |
jclass FindClass(const char* name) | 通過全限定類名(把**.** 換成 /)獲取對應的jclass,該函式用於載入本地定義的類。它將搜尋由CLASSPATH 環境變數為具有指定名稱的類所指定的目錄和 zip檔案 ,和GetObjectClass 一樣 |
jboolean IsSameObject(jobject |
判斷兩個引用是否指向同一jobeect |
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) | clazz1物件所表示的類或介面與指定的 clazz2 所表示的類或介面是否相同,或是否是其超類或超介面,可用於判斷確定 clazz1 的物件是否可安全地強制轉換為clazz2 |
jboolean IsInstanceOf( jobject obj, jclass clazz) | 判斷jobject是否是jclass型別的 |
1、非靜態成員Field操作的相關函式
1.1、GetFieldID獲取jfieldID
操作成員屬性前需要首先來獲取對應的jfieldID,但該方法不能用於獲取陣列的長度。
//引數clazz為傳入的jobject,name為Field名成,sig為對應的型別簽名
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
1.2、Get< type >Field獲取Field的值
通過以下通用形式:**NativeType Get< type >Field(jobject obj, jfieldID fieldID)**來獲取Field的值。
引數obj為jobject物件(非空),fieldID為GetFieldID返回的值
Get< type >Field Routine Name | Native Type |
---|---|
GetObjectField(jobject obj, jfieldID fieldID) | jobject |
GetBooleanField(jobject obj, jfieldID fieldID) | jboolean |
GetByteField(jobject obj, jfieldID fieldID) | jbyte |
GetCharField(jobject obj, jfieldID fieldID) | jchar |
GetShortField(jobject obj, jfieldID fieldID) | jshort |
GetIntField(jobject obj, jfieldID fieldID) | jint |
GetLongField(jobject obj, jfieldID fieldID) | jlong |
GetFloatField(jobject obj, jfieldID fieldID) | jfloat |
GetDoubleField(jobject obj, jfieldID fieldID) | jdouble |
1.3、設定Field的值
通過以下通用形式:**void Set< type >Field( jobject obj, jfieldID fieldID,NativeType value)**來設定Field
其中參obj為jobject物件(非空),fieldID為屬性ID,value為Field的引數型別的值。
Set< type >Field Routine | Native Type |
---|---|
SetObjectField(jobject obj, jfieldID fieldID, jobject value) | jobject |
SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) | jboolean |
SetByteField(jobject obj, jfieldID fieldID, jbyte value) | jbyte |
SetCharField(jobject obj, jfieldID fieldID, jchar value) | jchar |
SetShortField(jobject obj, jfieldID fieldID, jshort value) | jshort |
SetIntField(jobject obj, jfieldID fieldID, jint value) | jint |
SetLongField(jobject obj, jfieldID fieldID, jlong value) | jlong |
SetFloatField(jobject obj, jfieldID fieldID, jfloat value) | jfloat |
SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) | jdouble |
2、非靜態函式呼叫相關的函式
2.1、通過GetMethodID獲取methodID
呼叫函式前需要首先獲取methodID。
//其中引數clazz為傳入的jobject,name為函式名,sig為對應的函式簽名
jmethodID GetMethodID( jclass clazz, const char *name, const char *sig )
2.2、呼叫非靜態方法
使用env來呼叫對應的Call< Native Type>Method(jobject obj, jmethodID methodID, …) 、Call< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)、Call< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)和CallNonvirtual< Native Type>Method(jobject obj, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)、**CallNonvirtual< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)**系函式。
1、obj傳入的是物件例項,尤其是JNI層不要與jclass 搞混了
2、其中< Native Type>代表函式返回值的型別,如上表所示,比如CallVoidMethod() 代表呼叫的是無返回值型別的函式。
2、函式名後的是否有A、V是代表接收引數的形式,都沒有則代表通過不定引數的形式傳入實參,有A則代表通過陣列形式傳入實參,有V則代表通過va_list(指向變參列表的指標)形式傳入實參。
C++讓一個函式具有多型屬性需要顯式的宣告virtual關鍵字,如果一個方法被定義在父類中且在子類中被覆蓋,你也可以呼叫這個例項方法,JNI提供了一系列完成這些功能的函式:CallNonvirtual< Native Type>Method(jobject obj, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)*、CallNonvirtual< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)。為了呼叫一個定義在父類中的例項方法(其中CallNonvirtualVoidMethod也可以被用來呼叫父類的建構函式。JNI中建構函式可以和例項方法一樣被呼叫,呼叫方式也相似,只要傳入“< init >”作為方法名,“V”作為函式簽名),通常包含以下步驟:
- 使用GetMethodID從一個指向父類的引用當中獲取方法ID
- 傳入對應的物件、父類、方法ID和引數,並呼叫CallNonvirtual< Native Type>Method、CallNonvirtual< Native Type>MethodA、CallNonvirtual< Native Type>MethodV系中的一個
當然也可以指定呼叫子類的函式,至於呼叫哪個函式,是取決於CallNonvirtual< Native Type>Method、CallNonvirtual< Native Type>MethodA、CallNonvirtual< Native Type>MethodV的第二個引數決定的。
3、靜態Field成員操作
3.1、GetStaticFieldID獲取靜態Field的jfieldID
//引數clazz為傳入的jobject,name為Field名成,sig為對應的型別簽名
jfieldID GetStaticFieldID( jclass clazz,const char *name, const char *sig)
3.2、GetStatic< type >Field獲取Field的值
通過以下通用形式:**NativeType GetStaticField(JNIEnv *env, jclass clazz,jfieldID fieldID)**來獲取Field的值。
引數obj為jobject物件(非空),fieldID為GetFieldID返回的值
3.3、SetStatic< type >Field設定Field的值
通過以下通用形式:**void SetStatic< type >Field( jobject obj, jfieldID fieldID,NativeType value)**來設定Field
其中參obj為jobject物件(非空),fieldID為屬性ID,value為Field的引數型別的值。
4、靜態函式操作
4.1、 GetStaticMethodID獲取靜態方法ID
//其中引數clazz為傳入的jobject,name為函式名,sig為對應的函式簽名
jmethodID GetStaticMethodID(jclass clazz,const char *name, const char *sig);
4.2、呼叫靜態方法
使用env來呼叫對應的Call< Native Type>Method(jclass clazz, jmethodID methodID, …) 、Call< Native Type>MethodA(jclass clazz, jmethodID methodID, jvalue args)、Call< Native Type>MethodV(jclass clazz, jmethodID methodID, va_list args)和CallNonvirtual< Native Type>Method(jclass clazz, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jclass clazz, jmethodID methodID, jvalue args)、**CallNonvirtual< Native Type>MethodV(jclass clazz, jmethodID methodID, va_list args)**系函式,與呼叫非靜態函式對比除了字面上多了個Static之外,第一個引數也有所不同,在呼叫非靜態函式時第一個引數傳入的是jobject,而呼叫靜態函式時傳遞的是jclass。
1、obj傳入的是jclass位元組碼,尤其是JNI層不要與jobect物件例項搞混
5、字串操作的相關函式
函式名稱(env->函式名) | 功能說明 | 需要配套使用的函式 |
---|---|---|
jstring NewString(const jchar *unicodeChars, jsize len) | 從Unicode字元陣列構造一個新的java.lang.String物件 | ReleaseStringChars(jstring string, const jchar* chars) |
jsize GetStringLength( jstring string) | 返回字串的長度(Unicode 字元數) | |
const jchar * GetStringChars(jstring string, jboolean *isCopy) | 返回指向字串的Unicode字元陣列的指標( 在呼叫ReleaseStringchars函式之前,此指標有效) | ReleaseStringChars(jstring string, const jchar* chars) |
void ReleaseStringChars(jstring string, const jchar *chars) | 回收chars的記憶體空間 | |
void ReleaseStringUTFChars(jstring string, const char* utf) | 回收uft的記憶體空間 | |
jstring NewStringUTF(const char *bytes) | 從修改後的UTF-8編碼中的字元陣列構造一個新字串物件 | void ReleaseStringUTFChars(jstring string, const char* utf) |
jsize GetStringUTFLength(jstring string) | 以位元組為單位返回字串的 UTF-8 長度 | |
const jbyte GetStringUTFChars(jstring* string, jboolean *isCopy); | 返回指向位元組陣列的指標,該位元組陣列表示修改後的UTF-8編碼中的字串。 在ReleaseStringUTFChars()釋放之前有效,參見下文對isCopy的說明 | |
void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) | 從Java字串中擷取一段字元,其中str為字串物件,start為起始位置,len為擷取長度,buf為儲存擷取結果的緩衝區 | |
void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) | 從UTF-8字串中擷取一段字元,其中str為字串物件,start為起始位置,len為擷取長度,buf為儲存擷取結果的緩衝區 | |
const jchar * GetStringCritical(jstring string, jboolean *isCopy) | 當我們需要獲取字元陣列時,使用上面的函式都有可能或得到原始字串的拷貝,這會執行效率有些影響。如果我們想獲得指向原始字串指標就可以極大地優化執行效率。這兩個函式Get/ReleaseStringCritical來操作原始字串的直接指標。但是在這兩個函式之間不能存在任何會讓執行緒阻塞的操作。 | void ReleaseStringCritical(jstring string, const jchar* carray) |
6、陣列操作的相關函式
6.1、建立陣列
JNI為不同的型別定義了建立陣列的方法,對於已知具體型別(非Object)的呼叫形如jArrayType NewArray(jsize length) 的函式會返回對應的陣列
- PrimitiveType 為Java中對應的原始型別,ArrayType 為JNI中陣列的型別
函式 | ArrayType 陣列型別 | 陣列元素型別 | 說明 |
---|---|---|---|
jbooleanArray NewBooleanArray(jsize length) | jbooleanArray | jboolean | 返回的對應的陣列物件 |
jbyteArray NewByteArray(jsize length) | jbyteArray | jbyte | 同上 |
jcharArray NewCharArray(jsize length) | jcharArray | jchar | 同上 |
jshortArray NewShortArray(jsize length) | jshortArray | jshort | 同上 |
jintArray NewIntArray(jsize length) | jintArray | jint | 同上 |
jlongArray NewLongArray(jsize length) | jlongArray | jlong | 同上 |
jfloatArray NewFloatArray(jsize length) | jfloatArray | jfloat | 同上 |
jdoubleArray NewDoubleArray(jsize length) | jdoubleArray | jdouble | 同上 |
建立物件陣列的話就是通過函式**jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement)**構造新的陣列,它將儲存類 elementClass 中的物件且所有元素初始值均設為 initialElement。
6.2、訪問陣列和釋放快取
同樣地JNI為不同的型別定義了對應訪問陣列的方法,對於已知具體型別(非Object)的呼叫形如NativeType* Get< PrimitiveType >ArrayElements(ArrayType array, jboolean *isCopy)的函式會返回指向原始型別陣列的指標
- PrimitiveType 為Java中對應的原始型別,ArrayType 為JNI中陣列的型別
- isCopy這個引數很重要,上文中的也都一樣,它是一個指向Java布林型別的指標。在函式返回之後應當檢查這個引數的值,如果值為JNI_TRUE表示返回的字元是Java字串的拷貝,則可以對其中的值進行任意修改。反之為JNI_FALSE,表示這個字元指標指向原始Java字串的記憶體,這時候對字元陣列的任何修改都將會原始字串的內容。但如果無需考慮字元陣列的來源或者操作不會對字元陣列進行任何修改,可以傳入NULL。
簡單來說就是true則是把Java字串的內容拷貝到新記憶體中來,false則是直接指向Java中原始的記憶體地址。
函式 | ArrayType 陣列型別 | 陣列元素型別 | 說明 | 配套使用 |
---|---|---|---|---|
jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) | jbooleanArray | jboolean | 返回的是指向陣列首元素的指標 | ReleaseBooleanArrayElements 釋放 |
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) | jbyteArray | jbyte | 同上 | ReleaseByteArrayElements 釋放 |
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) | jcharArray | jchar | 同上 | ReleaseShortArrayElements 釋放 |
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) | jshortArray | jshort | 同上 | ReleaseBooleanArrayElements 釋放 |
jint* GetIntArrayElements(jintArray array, jboolean* isCopy) | jintArray | jint | 同上 | ReleaseIntArrayElements 釋放 |
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) | jlongArray | jlong | 同上 | ReleaseLongArrayElements 釋放 |
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) | jfloatArray | jfloat | 同上 | ReleaseFloatArrayElements 釋放 |
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) | jdoubleArray | jdouble | 同上 | ReleaseDoubleArrayElements 釋放 |
jobject GetObjectArrayElement(jobjectArray array, jsize index) | jobject | jobject | 返回物件陣列的元素 |
而且由於訪問陣列的過程中會產生區域性引用,為了避免記憶體洩漏,應該配套呼叫釋放記憶體的方法
void Release< PrimitiveType >ArrayElements (ArrayType array, jboolean elems,jint mode)*
mode取值 | 說明 |
---|---|
0 | 拷貝內容並釋放elems的快取即重新整理java陣列並釋放c/c++陣列(copy back the content and free the elems buffer) |
JNI_COMMIT | 拷貝內容並但不釋放elems的快取即只重新整理java陣列(copy back the content but do not free the elems buffer) |
JNI_ABORT | 直接釋放elems的快取(free the buffer without copying back the possible changes) |
6.3、設定物件陣列的元素
//如果要對java物件陣列的物件進行操作,你必須使用GetObjectArrayElement函式以jobject形式返回陣列的元素,然後再操作jobject
你也可以用SetObjectArrayElement函式把jobject放進java物件陣列
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);
6.4、擷取和設定非Object陣列
陣列的擷取操作即獲取陣列的子集,僅僅是針對具體型別(非Object)的,只要呼叫
**void Get< PrimitiveType >ArrayRegion( ArrayType array,jsize start, jsize len, NativeType *buf)**傳入對應的引數即可。
- 表示把陣列array從第start個元素開始的len個元素拷貝到buf地址所指向的記憶體區域
範圍設定陣列也是僅針對具體型別的,通過呼叫
*Set< PrimitiveType >ArrayRegion(ArrayType array,jsize start, jsize len, const NativeType buf)
- 表示把陣列array從第start個元素開始的len個元素設定到buf地址所指向的記憶體區域
6.5、獲取操作基本資料型別陣列的直接指標
與前文中關於操作原始字串類似,在某些情況下,當我們需要原始資料指標來進行一些操作,可以呼叫
void GetPrimitiveArrayCritical(jarray array, jboolean isCopy)函式來獲取一個指向原始資料的指標,同樣地存在一些限制,在呼叫void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode)**函式之前,兩個函式之間不能存在任何可能導致執行緒阻塞的操作,而且GC的執行會打斷執行緒,因此期間任何呼叫GC的執行緒都會被阻塞。
7、動態註冊和反註冊JNI函式
//向 clazz 引數指定的類註冊本地方法。methods 引數將指定 JNINativeMethod 結構的陣列,其中包含本地方法的名稱、簽名和函式指標。nMethods 引數將指定陣列中的本地方法數。
jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods)
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
反註冊JNI函式
//取消註冊類的本地方法。類將返回到連結或註冊了本地方法函式前的狀態。該函式不應在常規平臺相關程式碼中使用。相反,它可以為某些程式提供一種重新載入和重新連結本地庫的途徑。
jint UnregisterNatives( jclass clazz)
8、其他函式
函式 | 說明 |
---|---|
Throw | |
ThrowNew | |
ExceptionOccurred | |
ExceptionDescribe | |
ExceptionClear | |
FatalError | |
NewGlobalRef | |
DeleteGlobalRef | |
DeleteLocalRef |
未完待續…