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