1. 程式人生 > >Android NDK——必知必會之JNI的C++操作函式詳解和小結(三)

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
methodID, …)
通過反射構造方法建立物件,通過不定引數傳入對應的實參
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
ref1, jobject ref2)
判斷兩個引用是否指向同一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

未完待續…