1. 程式人生 > >android之JNI引數傳遞 (Java方法呼叫)

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的能力和潛力。我鼓勵你獲取