JNI的簡單介紹
Java Native Interface (JNI)是java本地調(diào)用接口,所謂的native就是調(diào)用c/c++的程序。
java調(diào)用C語言的情況一般有三種:
- 調(diào)用驅(qū)動。由于操作系統(tǒng)提供的驅(qū)動一般都是C接口,Java語言并不具備操作這些驅(qū)動的能力。
- 對于計(jì)算量比較大,處理數(shù)據(jù)比較多的模塊,java的效率沒有C高,所以希望用C去完成。
- 對于某些功能模塊,可能Java和C的效率差不多,但是C已經(jīng)寫好了,就不想用Java重寫了。
從程序的角度來說,主要關(guān)注兩種情況:
對于理解JNI的調(diào)用實(shí)現(xiàn),對于理解Android的源碼有幫助,因?yàn)锳ndroid系統(tǒng)中大量的使用了JIN。
Java訪問C
任何語言直接的交互都必須遵循一定的規(guī)則或者協(xié)議,只有這樣雙方才能理解各自的意圖。
java中定義native函數(shù),對于native函數(shù)只需要聲明,具體實(shí)現(xiàn)由C去實(shí)現(xiàn)。也就是說,native函數(shù)的
實(shí)現(xiàn)與聲明是分離的,java負(fù)責(zé)聲明,C負(fù)責(zé)實(shí)現(xiàn)。所以java在編譯是不會關(guān)心具體實(shí)現(xiàn),編譯時就不會出錯。
如何調(diào)用的呢?在調(diào)用之前java是不會關(guān)心是否已經(jīng)實(shí)現(xiàn),只有在調(diào)用native方法的時候,去找C生成的動態(tài)庫,如果
找不到動態(tài)庫,那么native方法就會報(bào)錯。
java如何找到C的函數(shù)的呢?
java的native函數(shù)和C中的函數(shù)存在一種映射關(guān)系,這個映射關(guān)系就是遵循的一種協(xié)議或者稱為規(guī)則。
比如,F(xiàn)ramework中AssetManager類中聲明了:
private native final void init()
該方法在C中對應(yīng)的是:
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
從上面的對應(yīng)關(guān)系可以看出,在C中的規(guī)則就是,包名+類名+方法名,并且中間用下劃線分割
第一個參數(shù)env,是JNIEnv對象,該對象代表一個Java虛擬機(jī)所運(yùn)行的環(huán)境,通過它可以訪問JVM內(nèi)部的各種對象;
第二個參數(shù)jobject和是調(diào)用該函數(shù)的對象,上面的例子指的就是AssetManager對象;每個這樣的C函數(shù)的參數(shù)至少
有這兩個參數(shù),如果native函數(shù)里有多個參數(shù),依次在后面排列,java的數(shù)據(jù)類型和JNI中的數(shù)據(jù)類型對應(yīng)關(guān)系,
自己可以去網(wǎng)上查詢一下。
當(dāng)java調(diào)用native函數(shù)時,編譯器會向native引擎?zhèn)鬟f調(diào)用者的包名,函數(shù)名及參數(shù)類型,native引擎
根據(jù)這些信息決定應(yīng)該調(diào)用具體的的哪個函數(shù)。
在android中,native引擎中的AndroidRuntime類提供了一個registerNativeMethods()函數(shù),通過此函數(shù)
定義java方法和C函數(shù)的映射關(guān)系。
生成 .h文件
java 的native方法和JNI中的c中的函數(shù)的對應(yīng)的定義文件頭文件(.h文件)可以手工寫,有些麻煩
并且還容易出錯,java提供了一個javah工具,通過該工具可以從一個java文件自動生成相應(yīng)的
投文件,網(wǎng)上查詢一下具體用法。
接下來就是根據(jù)頭文件去實(shí)現(xiàn)相應(yīng)的C代碼,然后生成動態(tài)庫。
如果想在java中調(diào)用native方法,需要使用在調(diào)用的代碼前面使用System.loadLibrary("lib_name")去
裝載該動態(tài)庫。
C訪問java
雖然C訪問java的情況不多見,不過也是能遇到的。
由于java中的函數(shù)在native引擎中并沒有直接的函數(shù)指針,java函數(shù)只能由java引擎去執(zhí)行,而不是C。所以訪問
java不能通過指針,只能通過參數(shù)接口。
java訪問c的時候,把類名,函數(shù)名,參數(shù)類型傳遞給native引擎,然后由native引擎處理C函數(shù),同理,
C調(diào)用java時,也需要把想要訪問的類名、函數(shù)名、參數(shù)傳遞給java引擎。
按照如下步驟:
- 獲取java對象的類
jclass cls = env->GetObjectclass(jobject);
jobject就是需要調(diào)用的誰的方法,java對象在JNI中的表示。 - 獲得java方法的Id值
jmethodId mid = env->GetMethodId(cls, "method_name", "([Ljava/lang/String;)V");
第一個參數(shù)是java對象對應(yīng)的類
第二個參數(shù)是java中的方法名稱
第三個參數(shù)是數(shù)據(jù)類型和返回值
解釋一下第三個參數(shù):
參數(shù)在括弧中,返回值在括弧外, ([Ljava/lang/String;)V" : [Ljava/lang/String代表參數(shù)類型,如果是類包名用
“/”分開,并且前面加上又給大寫的L。
java和native數(shù)據(jù)類型對應(yīng)表
java類型 | native類型 |
boolean | Z |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | L |
Object | 'L' + '包名'+';' |
short | S |
這個參數(shù)可以自己手工寫,還是那樣容易出錯,java提供了一個工具用來查看參數(shù)簽名,使用javap工具可以自動生成,自己從網(wǎng)上查詢一下即可。 - 調(diào)用函數(shù)
env->CallXXXMethod(jobject, mid, ret);
其中XXX是不同的類型,包括:Void,Oject,Boolean,Byte,Char,Short,Int,Long,Float,Double
第一個參數(shù)是調(diào)用的類的對象
第二個參數(shù)是上一步獲得的方法的id
第三個參數(shù)數(shù)是返回值
上面是調(diào)用方法,調(diào)用java的變量也類似:
- 獲取java對象的類
jclass cls = env->GetObjectclass(jobject); - 獲得變量的id
jfiledId fid = env->GetFiledId(cls, "fileName", "I"); - 獲取變量值
value = env->GetXXXField(env, jobject, fid);
在C中使用持久對象和保持持久對象
C中本身無法保存持久對象,把持久對象保存到一個int類型的變量上,使用的時候再強(qiáng)制轉(zhuǎn)型成對象。