android之JNI引數傳遞 (Java方法呼叫)
從Java 1.1開始,Java Native Interface (JNI)標準成為java平臺的一部分,它允許Java程式碼和其他語言寫的程式碼進行互動。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計 的,但是它並不妨礙你使用其他語言,只要呼叫約定受支援就可以了。讓我們看一些使用JNI的簡單例子吧。
使用java與本地已編譯的程式碼互動,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬體、作業系統進行互動,或者為了提高程式的效能。JNI標準至少保證原生代碼能工作在任何Java 虛擬機器實現下。
開始:如果你習慣了使用JNI,你就不會覺得它難了。既然本地方法是由其他語言實現的,它們在Java中沒有函式體。但是,所有原生代碼必須用本地關鍵詞 宣告,成為Java類的成員。清單A演示了一個簡單的類,它申明瞭一個本地的(native),靜態的(static)方法:sum。
寫完了你的Java類,接下來就要寫原生代碼。本地方法符號提供一個滿足約定的標頭檔案,使用Java工具可以很容易地建立它而不用手動去建立。你對 Java的class檔案使用javah命令,就會為你生成一個對應的C/C++標頭檔案。清單B就是為清單A的Test1類建立的標頭檔案。注意:它建立了 一個C/C++函式:Java_Test1_sum。
執行本地方法:一旦你有了這個標頭檔案,你就需要寫標頭檔案對應的本地方法,就像我在清單C做的那樣。注意:所有的本地方法的第一個引數都是指向JNIEnv結構的。 這個結構是用來呼叫JNI函式的,(我會在另一個章節中討論)。第二個引數jclass的意義,要看方法是不是靜態的(static)或者例項 (Instance)的。前者,jclass代表一個類物件的引用,而後者是被呼叫的方法所屬物件的引用。最後的兩個jint引數表示了Java方法的 int引數。
返回值和引數型別根據等價約定對映到本地C/C++型別,如表A所示。有些型別,如清單B裡面的兩個jint引數,在原生代碼中可直接使用,而其他型別只有通過JNI呼叫操作。
表A
Java型別 | 本地型別 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號的8位整型 |
char | jchar | C/C++無符號的16位整型 |
short | jshort | C/C++帶符號的16位整型 |
int | jint | C/C++帶符號的32位整型 |
long | jlong | C/C++帶符號的64位整型e |
float | jfloat | C/C++32位浮點型 |
double | jdouble | C/C++64位浮點型 |
Object | jobject | 任何Java物件,或者沒有對應java型別的物件 |
Class | jclass | Class物件 |
String | jstring | 字串物件 |
Object[] | jobjectArray | 任何物件的陣列 |
boolean[] | jbooleanArray | 布林型陣列 |
byte[] | jbyteArray | 位元型陣列 |
char[] | jcharArray | 字元型陣列 |
short[] | jshortArray | 短整型陣列 |
int[] | jintArray | 整型陣列 |
long[] | jlongArray | 長整型陣列 |
float[] | jfloatArray | 浮點型陣列 |
double[] | jdoubleArray | 雙浮點型陣列 |
※ JNI型別對映
最後一步是把原生代碼編譯成共享庫(比如,UNIX的so檔案,Windows的dll檔案)。在Java中呼叫方法前,共享庫須通過System.loadLibrary匯入。最常用的方式是在類的靜態(static)初始化器裡做這這個工作。
在原生代碼中訪問JNI
我舉的例子很簡單,並不能滿足演示怎樣寫JNI方法的目標。現在,讓我們看一些高階的,通過JNIEnv結構使用非簡單型別的例子。
JNI通過函式的形式提供了很多功能,供原生代碼通過指向JNIEnv結構的指標呼叫;它作為第一個引數傳遞給每個本地方法。JNI函式的呼叫有下面幾種格式(這裡,假設env是指向JNIEnv的指標):
//C 格式
(*env)-><jni function>( env, <parameters> )
//C++ 格式
env-><jni function>( < parameters> )
這篇文章中接下來的例子我將會用C++格式。
使用陣列:JNI通過JNIEnv提供的操作Java陣列的功能。它提供了兩個函式:一個是操作java的簡單型陣列的,另一個是操作物件型別陣列的。
因為速度的原因,簡單型別的陣列作為指向本地型別的指標暴露給原生代碼。因此,它們能作為常規的陣列存取。這個指標是指向實際的Java陣列或者Java陣列的拷貝的指標。另外,陣列的佈置保證匹配本地型別。
為了存取Java簡單型別的陣列,你就要要使用GetXXXArrayElements函式(見表B),XXX代表了陣列的型別。這個函式把Java陣列看成引數,返回一個指向對應的本地型別的陣列的指標。
表B
函式 | Java陣列型別 | 本地型別 |
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
JNI陣列存取函式
當你對陣列的存取完成後,要確保呼叫相應的Relea***XXArrayElements函式,引數是對應Java陣列和 GetXXXArrayElements返回的指標。如果必要的話,這個釋放函式會複製你做的任何變化(這樣它們就反射到java陣列),然後釋放所有相 關的資源。
為了使用java物件的陣列,你必須使用GetObjectArrayElement函式和SetObjectArrayElement函式,分別去get,set陣列的元素。GetArrayLength函式會返回陣列的長度。
清單D包含了一個簡單的類,它演示了原生代碼如何使用Java陣列。這個本地實現迴圈遍歷一個整型(int)陣列,返回這些元素的總和。為簡單起見,這個清單包含了java程式碼和本地實現。我已經省略了標頭檔案,它可以很方便地通過javah得到。
在原生代碼中訪問JNI
使用物件JNI提供的另外一個功能是在原生代碼中使用Java物件。通過使用合適的JNI函式,你可以建立Java物件,get、set 靜態(static)和例項(instance)的域,呼叫靜態(static)和例項(instance)函式。JNI通過ID識別域和方法,一個域或 方法的ID是任何處理域和方法的函式的必須引數。
表C列出了用以得到靜態(static)和例項(instance)的域與方法的JNI函式。每個函式接受(作為引數)域或方法的類,它們的名稱,符號和它們對應返回的jfieldID或jmethodID。
表C
函式 | 描述 |
GetFieldID | 得到一個例項的域的ID |
GetStaticFieldID | 得到一個靜態的域的ID |
GetMethodID | 得到一個例項的方法的ID |
GetStaticMethodID | 得到一個靜態方法的ID |
※域和方法的函式
如果你有了一個類的例項,它就可以通過方法GetObjectClass得到,或者如果你沒有這個類的例項,可以通過FindClass得到。符號是從域的型別或者方法的引數,返回值得到字串,如表D所示。
表D
Java 型別 | 符號 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects物件 | Lfully-qualified-class-name;L類名 |
Arrays陣列 | [array-type [陣列型別 |
methods方法 | (argument-types)return-type(引數型別)返回型別 |
※確定域和方法的符號
一旦你有了類和方法或者域的ID,你就能把它儲存下來以後使用,而沒有必要重複去獲取。
有幾個分別訪問域和方法的函式。例項的域可以使用對應域的GetXXXField的變體函式訪問。GetStaticXXXField函式用於靜態型別。設定域的值,用SetXXXField 和SetStaticXXXField函式。表E包含了所有訪問域的函式列表。
表E
Java 型別 | Method方法 |
boolean | GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField |
byte | GetByteField, GetStaticByteField, SetByteField, SetStaticByteField |
char | GetCharField, GetStaticCharField, SetCharField, SetStaticCharField |
short | GetShortField, GetStaticShortField, SetShortField, SetStaticShortField |
int | GetIntField, GetStaticIntField, SetIntField, SetStaticIntField |
long | GetLongField, GetStaticLongField, SetLongField, SetStaticLongField |
float | GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField |
double | GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField |
object | GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField |
※訪問域的函式
另外,方法的訪問是由CallXXXMethod 函式和CallStaticXXXMethod函式完成的,XXX表明了方法的返回值型別。這些函式的變體允許傳遞陣列引數 (CallXXXMethodA and CallStaticXXXMethodA)或者傳遞一個可變大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。
一個完整的列表
表F:一個完整的列表
返回型別 | 函式 |
boolean | CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV |
byte | CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV |
char | CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV |
short | CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV |
int | CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV |
long | CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV |
float | CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV |
double | CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV |
void | CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV |
object | CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV |
※方法訪問函式
清單E演示瞭如何在原生代碼中呼叫方法。本地方法printRandom得到了靜態方法Math.random的ID,並且呼叫它幾次,打印出結果。例項方法也一樣處理。
當你關注java的擴充套件時,JNI是一個強大的工具,它不會嚴重降低可移植性。我這裡只是接觸它的表面,僅僅向你演示了JNI的能力和潛力。我鼓勵你獲取