標籤:
聲明:歡迎轉載,轉載時請註明出處!http://blog.csdn.net/flydream0/article/details/7371692
1 簡述
JNI是Java Native Interface的縮寫,中文為JAVA本地調用。從Java1.1開始,Java Native Interface(JNI)標準成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行互動。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他語言,只要呼叫慣例受支援就可以了。
由於Android的應用程式層的類都是以Java寫的,這些Java類編譯為Dex型式的位元組碼之後,必須依靠Dalvik虛擬機器來運行,在Android中Dalvik虛擬機器扮演很重要的角色.而Android中介軟體是由C/C++寫的,這些C/C++寫的組件並不是在Dalvik虛擬機器上啟動並執行。那麼應用程式層上的Java代碼又是如何與C/C++寫的組件之間又是如何溝通的?
2 載入.so檔案
System.loadLibrary(*.so檔案);
在java代碼中,可以通過loadLibrary要求VM裝載so檔案,java代碼一般如下形式:
public class jnitest { static { System.loadLibrary("jnitest"); } //...}上述代碼運行時將會在/system/lib/目錄下尋找libjnitest.so檔案,將載入VM,這樣,java代碼和C組件之間就構成了聯絡,接下來就可以通過一些方法可以相互調用了.
3 JNI_OnLoad與JNI_OnUnload
在Android中,當程式在java層運行System.loadLibrary("jnitest");這行代碼後,程式會去載入libjnitest.so檔案,與此同時,產生一個"Load"事件,這個事件觸發後,程式預設會在載入的.so檔案的函數列表中尋找JNI_OnLoad函數並執行,與"Load"事件相對,當載入的.so檔案被卸載時,“Unload”事件被觸發,此時,程式預設會去在載入的.so檔案的函數列表中尋找JNI_OnUnload函數並執行,然後卸載.so檔案。需要注意的是,JNI_OnLoad與JNI_OnUnload這兩個函數在.so組件中並不是強制要求的,使用者也可以不去實現,java代碼一樣可以調用到C組件中的函數,在接下來的章節中會講到這點.
之所以在C組件中去實現這兩個函數(特別是JNI_OnLoad函數),往往是做一個初始化工作或“善後”工作。可以這樣認為,將JNI_ONLoad看成是.so組件的初始化函數,當其第一次被裝載時被執行(window下的dll檔案也可類似的機制,在_DLL_Main()函數中,通過一個swith case語句來識別當前是載入還是卸載)。將JNI_OnUnload函數看成是解構函式,當其被卸載時被調用。
由此看來,就不難明白為什麼很多jni C組件中會實現JNI_OnLoad這個函數了。 一般情況下,在C組件中的JNI_OnLoad函數用來實現給VM註冊介面,以方便VM可以快速的找到Java代碼需要調用的C函數。(此外,JNI_OnLoad函數還有另外一個功能,那就是告訴VM此C組件使用那一個JNI版本,如果未實現JNI_OnLoad函數,則預設是JNI 1.1版本)。
4 顯式註冊native方法4.1 顯式註冊的作用:
應用程式層的Java類別通過VM而調用到native函數。一般是通過VM去尋找*.so裡的native函數。如果需要連續呼叫很多次,每次都需要尋找一遍,會多花許多時間。此時,C組件開發人員可以將本地函數向VM進行註冊,以便能加快後續調用native函數的效率.可以這麼想象一下,假設VM內部一個native函數鏈表,初始時是空的,在未顯式註冊之前此native函數鏈表是空的,每次java調用native函數之前會首先在此鏈表中尋找需要尋找需要調用的native函數,如果找到就直接使用,如果未找到,得再通過載入的.so檔案中的函數列表中去尋找,且每次java調用native函數都是進行這樣的流程,因此,效率就自然會下降,為了克服這樣現象,我們可以通過在.so檔案載入初始化時,即JNI_OnLoad函數中,先行將native函數註冊到VM的native函數鏈表中去,這樣一來,後續每次java調用native函數時都會在VM中的native函數鏈表中找到對應的函數,從而加快速度.
註: 在Android 源碼開發環境下,大多採用顯示註冊native方法 .
4.2 在Android源碼開發模組下有兩種方法可以實現顯示註冊native方法:方法一: 使用JNIHelp.h標頭檔中定義的jniRegisterNativeMethods來實現.
如~/WORKING_DIRECTORY/frameworks/base/services/jni/com_android_server_location_GpsLocationProvider.cpp:
注:此檔案同級目錄中的其它cpp檔案大多採用此種方法進行native方法顯式註冊.
static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native}, {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported}, {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}, {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup}, {"native_set_position_mode", "(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode}, {"native_start", "()Z", (void*)android_location_GpsLocationProvider_start}, {"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop}, {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data}, {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status}, {"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea}, {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time}, {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location}, {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra}, {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data}, {"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open}, {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed}, {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed}, {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id}, {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid}, {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server}, {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response}, {"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message}, {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state}, {"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },};int register_android_server_location_GpsLocationProvider(JNIEnv* env){ return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));}其中jniRegisterNativeMethods和NELEM都是在標頭檔JNIHelp.h定義的,得:
#include "JNIHelp.h"
在Android.mk檔案中得加上:
LOCAL_SHARED_LIBRARIES +=libnativehelper
方法二:使用AndroidRuntime::registerNativeMethods
如~/WORKING_DIRECTORY/frameworks/base/media/jni/android_media_MediaPlayer.cpp:
註:目前的目錄下其它cpp檔案大多採用此種方法進行顯式native註冊.
static JNINativeMethod gMethods[] = { {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource}, { "_setDataSource", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSourceAndHeaders }, {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, {"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo}, //...}// This function only registers the native methodsstatic int register_android_media_MediaPlayer(JNIEnv *env){ return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods));}其中AndroidRuntime::registerNativeMethods是在標頭檔android_runtime/AndroidRuntime.h中定義,使用時得:
#include "android_runtime/AndroidRuntime.h"
且得在Android.mk檔案中加上:
LOCAL_SHARED_LIBRARIES += libandroid_runtime
有關函數命名,請參考部落格內另一篇文章: Android下如何通過JNI方法向上提供介面總結 。
註: 以上兩種顯式註冊native方法都只是適用於Android源碼開發環境下,至於在Android NDK環境下如何顯式註冊native方法,暫時還沒有研究過,且此兩種方法目前NDK還不支援,NDK開發模式下一般採用隱式註冊native函數,即接下來要講的內容.
5 隱式註冊native方法
前面第3節已經講到,JNI_OnLoad和JNI_OnUnload函數並不是強制要求實現的,在這種情況下,就相當於在載入.so檔案時,沒有了初始化函數,既然沒有了初始化函數,那顯式註冊native方法也行不通了。那這個時候又該如何讓應用程式層的java代碼調用下層的C函數呢?
幸運地是,即使我們不使用任何代碼做native函數顯式註冊,應用程式層的java代碼在調用native函數時,也會採用預設的方法到轉入的.so檔案中的函數列表中尋找對應的native 函數,只不過,這個native函數與java類型中聲明的native函數的名字之前有一種預設的對應關係。如:
java類的native成員函數:public native int socket_send(int cmdid,String argus);預設會在.so檔案中的函數列表中尋找jint JNICALL Java_com_hase_bclm_bclm_socket_1send(JNIEnv *env, jobject obj, jint cmdid, jstring argus);函數,一旦找到此對應的函數,VM就會將此native函數自動註冊到VM內部的native函數鏈表中,以便加快後續相同jni調用.
可接下來的問題是,作為碼農的我們,又是如何知道java native成員函數對應著C組件中的native 函數的名字呢?簡單地函數名字也許我們能搞定,複雜一點的就要傻眼了。同樣幸運地是,JDK提供了一個javah工具,可以用這個工具來自動通過.class檔案來產生C組件的標頭檔.
比如你用java寫了一個xxx.java檔案,裡邊的java類型裡面聲明了一些native成員函數,此xxx.java檔案編譯後會產生xxx.class檔案,那麼就在你產生的xxx.class包所在目錄輸入命令列:
$javah -jni com.packagename.yourclassname
就會在目前的目錄下產生一個標頭檔。
比如:
javah -jni com.test.example
則在目前的目錄下產生 com_test_example.h標頭檔.
目前的目錄下的com結構為:com/test/example.class
再將此標頭檔拷貝到你的C組件工程內,實現其中聲明的native函數即可.
之前在顯式註冊native函數的相關章節中已經說明,顯式註冊是將native函數添加到VM內部的native函數鏈表中,以加快後續jni調用的效率,其實在隱性native 註冊時,每一次執行某個jni調用時,VM在.so函數列表中找到對應的native函數後,同樣也會將其註冊到VM內部的native函數鏈表中,由此看到,隱式native註冊的方法除了第一次執某個jni調用時會稍微速度慢點外,後續同樣的調用就會直接在VM內部的native函數鏈表中找到對應的native函數,這樣看來,顯式與隱式註冊native方法,其實效率相差無幾.
OK,到此結束!
Android Jni調用淺述