Android Jni調用淺述

來源:互聯網
上載者:User

標籤:

聲明:歡迎轉載,轉載時請註明出處!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調用淺述

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.