android framework 之JNI

Java Native Interface ( JN I)是Java本地接口,所謂的本地(native) —般是指C/C++ ( 如下統稱C)
語言。當使用Java進行程序設計時,通常主要有三種狀況須要C/C++語言的協助。
• 調用驅動。因爲操做系統所提供的驅動通常都是C/C++ 接口,Java語言自己不具有操做這些驅動的
能力。
• 對於某些大量數據處理的模塊,Java的效率可能遠低於C/C++,所以,程序員但願使用C/C++ 去完成。
• 對於某些功能模塊,可能Java和C/C++ 的效率差很少,可是這些模塊已經存在已有的C/C++ 代碼,程序
員不想再用Java重寫,而只想從新利用已有的C/C++ 代碼。
這就是Java提出JNI概念的緣由。不管出於什麼緣由,從程序的角度來看,JNI接口主要包含兩種
狀況。第一種是從Java中訪問C/C++,第二種是從C/C++ 中訪問Java,只要解決了這兩個問題,那麼就能夠任
意進行Java和C/C++ 的應用組合。Framework中大量使用JNI完成本地接口的實現,所以,理解JNI是閱讀kernel的重中之重。java

1. java 訪問 c、c++android

Java中能夠定義某個函數爲native類型,對於native函數,只須要聲明便可,由於該函數的實現是
native的,即由相應的C 去實現。Java編譯器遇到native函數時,不會關心該函數的具體實現,所以,
編譯上不會出任何差錯。
程序運行時,在調用native方法以前,程序員必須把C 所生成的動態庫裝載進來,不然程序會因
爲找不到相應的native方法而出錯。關於如何把C 程序編譯爲動態庫,將在第18章中的Make系統中
介紹,這牽扯到包含jni.h頭文件等及編譯選項的設置。
當調用native函數時,Java會自動產生一個對應的C 中的函數名稱,由於Java中聲明的函數名稱
和C 中實現的函數名稱是不一樣的。其關係爲,後者等於包名加前者的名稱,而且中間如下畫線分隔,
好比,Framework中AssetManager類中聲明瞭如下方法:c++

AssetManager.java (base\core\java\android\content\res)程序員

private native final String[] getArrayStringResource(int arrayRes);編程

android_util_AssetManager.cpp (base\core\jni)中找到JNI關係映射數組:數組

static JNINativeMethod gAssetManagerMethods[] = {
...
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
{ "getArrayStringInfo","(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
{ "getArrayIntResource","(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
...
}函數

同時,咱們能夠在android_util_AssetManager.cpp (base\core\jni)中找到void*) android_content_AssetManager_getArrayStringResource的定義。工具

static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
jint arrayResId)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return NULL;
}操作系統

.......設計

res.unlockBag(startOfBag);
return array;
}

以上這種映射關係並非Java編譯器內含的,程序員徹底能夠改變,但這是一種編程規範。事實上,
當Java調用native時,編譯器會向native引擎傳遞調用者的包名,以及函數名稱,還有參數類型,僅
此而已,固然這也足矣,native引擎根據這些信息決定應該具體調用哪一個本地函數。native引擎中
AndroidRuntime類提供了一個registerNativeMethods()函數,能夠經過該函數來定義Java native函數和C
函數名稱的映射關係。

以下:

int register_android_content_AssetManager(JNIEnv* env)
{
...;
return AndroidRuntime::registerNativeMethods(env,
"android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods));
}


在產生的C/C++ 函數中,會包含至少兩個參數。前者是JNIEnv對象,該對象是一個Java虛擬機(JVM)
所運行的環境,至關於JVM的「管家」,經過它能夠訪問JVM內部的各類對象;第二個參數jobject是
調用該函數的對象,本例中指的就是AssetManager對象,第三個參數是java函數中的參數。

在以上的轉換關係中,你們可能會注意到native中所使用的類型和Java中有所不一樣。好比Java中
的int在native中爲jint,返回值中String□變爲jobjectArray,這些具體的定義其實是在jni.h中,該文
件所在的路徑爲:

Jni.h (kernel\android\jb\development\ndk\platforms\android-3\include)

裏面還有其餘類型的定義。

 

同時,不一樣的platforms會有不一樣的定義,這個很好理解,由於Java的類型是跨平臺的,而各自平臺的
CPU數據寬度是不一樣的,因此必須有各自的類型定義。
以上介紹了 Java和C 函數的名稱轉換,那麼,具體怎麼操做呢?在程序設計時,若是你已經定義
好了 Java代碼,如何實現相應的native C 代碼呢?
你可能會想:「那就按照這種轉換關係,手工編寫相應的C 代碼,而後編譯成動態庫,並在Java
代碼執行時加載該庫就能夠了(須要使用System.loadLibrary( 「 lib—name」 )函數裝載該庫。)。

沒錯,是這個樣子,爲了輔助你這樣作,Java還提供了一個javah工
具,該工具能夠從一個Java文件自動生成相應的頭文件,剩下的就是你根據這些頭文件再實現具體的
內部代碼便可。

 

2.c/c++ 訪問 java 

這種狀況彷佛比較少,C 爲何還要訪問Java呢?這個也容易理解,若是C 中須要使用Java的某
個變量而進行相應的處理,或者C 中也想調用Java中的某個函數完成某些操做,那麼C 就要訪問Java。
因爲Java中的函數在native引擎中並無直接的函數指針,Java函數只能由Java引擎去執行,而
不是C。因此,C 訪問Java不能經過函數指針,而只能經過通用的參數接口,正如Java調用C 同樣。
Java把類名、函數名稱、參數類型傳遞給native引擎,而後由native引擎處理C 函數,同理,C 調用
Java時,也須要把想要訪問的類名、函數名稱、參數傳遞給Java引擎。其步驟以下:

(1)獲取Java對象的類。
cls = env->GetObjectClass(jobject);

其中env爲Java調用C 函數時的第一個參數,這意味着C 調用Java函數只能在Java調用C 函數
中進行,不然沒法獲取env變量。換句話說,對於C 來說,就是「你不惹我,我不惹你」。jobject爲第
二個參數。cls的類型是jelass。

2.獲取Java函數的id 值。
jmethodld mid = env->GetMethodId(cls,"method_name","([Ljava/lang/String;)V");

該方法中第二個參數爲Java中的函數名稱,第三個參數值得注意,它表明了 Java函數的參數和返
回值,參數在括弧之中,返回值在括弧以外。本例中,參數[Ljava/lang/String表明了 String類型的參數,
因爲String自己是一個類,而不是Java的原子類型,因此前面加了包的名稱,並用斜線分隔,最前面
還要用一箇中括弧進行標識,後面還要用分號隔離。返回值V 表明void.

3.找到了函數後,就能夠調用該函數了。
env->CallXXXMethod(jobject,mid,ret);
其中XXX表明了函數的返回值類型,具體包括Void、Object、Boolean、Byte、Char、Short、Int、
Long, Float、Double。在我看來,JNI提供的這種按類型調用並非必需的,只是爲了某種靈活,由於
該函數的第三個參數是保存返回值的變量,因此JN I內部徹底能夠根據ret的類型來選擇把Java的執行
結果進行格式轉換。第二個參數mid即爲第二步中所得到的函數id。

經過以上三步,實現了 C 中調用Java函數的目標。還有一個問題,C 中如何訪問Java中的變量呢?
實現步驟以下:

(1)獲取Java對象的類。
cls = env->GetObjectClass(jobject);

2.獲取Java變量的id 值。
jmethodld fid = env->GetfiledId(cls,"filed_name","I");

參數filed_name 爲Java變量的名稱,第三個參數爲變量的類型,其格式與上面相同。

3.獲取變量值。

value=env->GetXXXField(env,jobject,fid);該函數的參數與上面的顯著不一樣,其中第」、第二個參數爲原裝Java訪問C 函數的前兩個參數,該方法以返回值的方式獲取變量值,而不是經過參數引用。

相關文章
相關標籤/搜索