基於Android2.3.5系統:JNI與HAL執行個體解析[一]
一:Android系統下JNI簡介
Android系統下的JNI的全稱是:Java Native Interface (JNI),JNI標準是java平台的一部分,它允許Java代碼和其他語言寫的代碼進行互動。JNI 是本地編程介面,它使得在 JAVA 虛擬機器 (VM) 內部啟動並執行 Java 代碼能夠與用其它程式設計語言(如 C、C++ 和組合語言)編寫的應用程式和庫進行互動操作。
二:為什麼Java要推出JNI
JNI在Android系統中有著廣泛的應用。Android系統底層都是C/C++實現的,上層提供的API都是Java的,Java通過JNI調用底層的實現。
Java世界虛擬機器使用Native語言編寫的,虛擬機器運行在具體的平台上,虛擬機器本身無法做到與平台無關,jni可以對java層屏蔽不同平台操作的差異,這樣就能實現java本身平台無關特性。適應已經用Native語言實現的技術。一些效率的問題需要Native語言實現。
在Java中使用JNI主要是為了以上幾點而實現的,在Anroid中的API多媒體介面MediaPlayer類,其實底層通過JNI調用libmedia庫。由於JNI的存在可以讓我們重用很多已經存在C/C++的庫,省去了重複開發的麻煩,並且可以利用很多開源的庫(Android庫中就有很多開源庫,比如libjpeg,libpng等等),並且讓我們開發的程式更有效率(C/C++代碼發揮硬體最佳效能)。
三:Java中JNI調用過程
在Java的世界裡,JNI對於應用本身來說,可以看做一個代理模式,對於開始者來說,需要使用C/C++來實現一個代理程式來實際操作目標原生函數,Java程式中則是JVM通過載入並調用JNI程式間接地調用目標原生函數。其調用的如下:
圖一:Android下JNI調用過程(Load DLL,DLL根據不同的環境而定)
Android系統Java下JNI程式開發的一般操作步驟如下:
編寫JAVA中的調用類;用javah產生C/C++原生函數的標頭檔;C/C++中調用需要的其他函數功能,實現原生函數;將項目依賴的所有原生庫和資源加到java項目的路徑中;產生JAVA程式;發布JAVA應用和so庫;
四:Android系統下JNI文法
Android系統Java下的JNI對於應用本身來說,可以看做一個代理模式,對於開始者來說,需要使用C/C++來實現一個代理程式來實際操作目標原生函數,基本的資料類型轉換,如下所示:
圖二:基礎資料型別 (Elementary Data Type)轉換表
備忘:符號屬性是指在JNI環境下
引用資料類型轉換表,如下所示:
圖三:引用資料類型轉換表
五:Java下JNI註冊函數
Java下的JNI命名規則:JNI層需要將JAVA函數名稱(包括包名)中的“.”換成“_”,用這種方式可以找到自己JNI層的路徑以及檔案。註冊Native函數有兩種方法:靜態註冊和動態註冊。靜態註冊靜態就是根據函數名來建立java和jni函數之間的關聯,而且要求jni層函數的名字必須遵循特定的格式,其缺點在於:需要編譯所有的java聲明的類;javah產生的jni層函數特別長;初次調用native函數時要根據名字搜尋對應的jni層函數來建立關聯聯絡,這樣影響效率。因此這裡只詳細的講解動態註冊的方式,Java native與JNI通過JNINativeMethod的結構來建立聯絡,其結構內容如下:
typedef struce { const char* name; //java中native函數的名字,不用攜帶包的路徑 const char*signature; //java函數的簽名資訊,用字串表示,是參數類型和傳回值類型的組合 void *fnptr; //JNI層對應的函數指標,注意他是void*的類型。 } JNINativeMethod; 用一個Anroid系統下的執行個體來感性的認識和說明一下,(請到網上去下載Anroid2.3.4的OK6410的Anroid代碼,跟本文的路徑一致,因為我自己把這個Android2.3.4的OK6410移植到Android2.3.5的平台上)路徑為android2.3.5/frameworksaseservicesorlinx_pwm_jniPwmJniService.cpp,這是一個PWM控制的JNI,具體的如下所示:
圖四:JNINativeMethod方法 Android提供一個RegisterNatives(JNIEnv *env, Const char* className, Const JNINativeMethod* gMethods, int numMethods)來實現這個功能,其實還有一個在AndroidRuntime.cpp中也實現了一個RegisterNatives函數,兩者的功能是相同的,如下所示:
我們來看看PwmJniService.cpp函數中的JNI動態註冊吧,如下所示:
在JNI_OnLoad函數中,會去調用register_forlinx_server_PwmService()這個函數,而這個函數的定義如下所示:
一起來看看這行代碼:static const char* const kClassName =forlinx_pwm_server/server/PwmService;,就是通過kClassName找到相應的庫的,而這個PwmService就是在Android2.3.5/frameworksaseservicesorlinx_pwm_serverserverPwmService.java檔案,如下所示:
也就是說Java層通過這種手段實現了Java層去調用C/C++層的庫,這樣就將Anroid底層給封裝起來了,而在Android的JNI層會去調用Android的HAL層,而HAL層調用的就是Linux的驅動!!
六:JNI的記憶體回收
Java中建立的對象最後由記憶體回收行程來回收和釋放記憶體的,而對於JNI,直接對對象進行引用後不會增加引用計數值,當從JNI返回後在JNI引用的對象有可能被記憶體回收行程回收,而在JNI下面記錄的指標有可能是野指標,為瞭解決此類問題,JNI規範提供如下三類引用:
Local Refrence:本地引用,在JNI層函數中使用的非全域變數都是採用Local Refrence,它包含函數調用時傳入的jobject和在JNI層函數中建立的jobject,其最大的特點是一旦JNI層函數返回,這些jobject就可能被記憶體回收了。Global Refrence:全域引用,這類引用如果程式不主動調用銷毀介面將一直駐留在記憶體中。Weak Global Refrence:弱全域引用,在使用過程中有可能被釋放,調用JNIEnv中IsSameObject函數來判斷是否仍然存在。
七:Java下JNI函數的簽名資訊 Android系統下Jave的JNI的簽名Signature的書寫規則如下: ()中的字元表示參數,後面的則代表傳回值,如“()V”就表示void func(), “(II)V”表示void func(int, int)等。具體的每個字元對應關係如下:
如果java函數的參數是class,則以“L”開頭,以“;”結尾,中間是用“/”隔開的包及類名,對應的C函數名的參數則為jobject,一個例外是String類,其對應類為jstring。如下所示:
Ljava/lang/String; String jstring Ljava/net/Socket; Socket jobject
以android2.3.5/frameworksaseservicesorlinx_pwm_jniPwmJniService.cpp中的static const JNINativeMethod gMethods[]數組函數為例子,如下所示:
選取forlinx_set_freq函數來說明JNI簽名的問題,在中可以看到
{ _set_freq, (I)Z, (void *)forlinx_set_freq }, 這樣的定義方式,對照上表中的簽名解析表格,我們就一目瞭然啦。forlinx_set_freq函數的定義如下所示:
可以看出來forlinx_set_freq的傳回值類型為:
jboolean,而
jboolean所代表的JNI簽名為:Z,而
jint freq是forlinx_set_freq函數中需要設定的一個值,而
jint所代表的JNI的簽名為:I 因此forlinx_set_freq函數的簽名資訊為:
(I)Z。