標籤:
JNI(Java Native Interface,JAVA原生介面)
使用JNI可以使Java代碼和其他語言寫的代碼(如C/C++代碼)進行互動。
問:為什麼要進行互動?
- 首先,Java語言提供的類庫無法滿足要求,且在數學運算,即時渲染的遊戲上,音視頻處理等方面上與C/C++相比效率稍低。
- 然後,Java語言無法直接操作硬體,C/C++代碼不僅能操作硬體而且還能發揮硬體最佳效能。
- 接著,使用Java調用本地的C/C++代碼所寫的庫,省去了重複開發的麻煩,並且可以利用很多開源的庫提高程式效率。
C語言常見術語:
庫函數:
- 為了代碼重用,在C語言中提供了一些常用的、用於執行一些標準任務(如輸入/出)的函數,這些函數事先被編譯,並產生目標代碼,然後將產生的目標代碼打包成一個庫檔案,以供再次使用。 庫檔案中的函數被稱為庫函數,庫檔案被稱為函數庫。
- 在Windows中C語言庫函數中的中間代碼都是以.obj為尾碼的,Linux中是以 .o為尾碼。
提示:單個目標代碼是無法直接執行的,目標代碼在運行之前需要使用串連程式將目標代碼和其他庫函數串連在一起後產生可執行檔檔案。 Windows下.dll的檔案 , linux下 .so .a的檔案.
標頭檔:xxx.h
- 標頭檔中存放的是對某個庫中所定義的函數、宏、類型、全域變數等進行聲明,它類似於一份倉庫清單。若使用者程式中需要使用某個庫中的函數,則只需要將該庫所對應的標頭檔include到程式中即可。
- 標頭檔中定義的是庫中所有函數的函數原型。而函數的具體實現則是在庫檔案中。
- 簡單的說:標頭檔是給編譯器用的,庫檔案是給連接器用的。
- 在連結器串連程式時,會依據使用者程式中匯入的標頭檔,將對應的庫函數匯入到程式中。標頭檔以.h為尾碼名。
函數庫:
- 動態庫:在編譯使用者程式時不會將使用者程式內使用的庫函數串連到使用者程式的目標代碼中,只有在運行時,且使用者程式執行到相關函數時才會調用該函數庫裡的相應函數,因此動態函數庫所產生的可執行檔比較小。
- 靜態庫:在編譯使用者程式時會將其內使用的庫函數串連到目標代碼中,程式運行時不再需要靜態庫。使用靜態庫產生可執行檔比較大。
在Linux中:
- 靜態庫命名一般為:lib+庫名+.a 。
- 如:libcxy.a 其中lib說明此檔案是一個庫檔案,cxy是庫的名稱,.a說明是靜態。
- 動態庫命名一般為:lib+庫名+.so 。.so說明是動態。
交叉編譯:
- 將中間代碼串連成當前電腦可執行檔二進位程式時,串連程式會根據當前電腦的CPU、作業系統的類型來轉換。
根據啟動並執行裝置的不同,可以將cpu分為:
- arm結構 :主要在移動手持、嵌入式裝置上。
- x86結構 : 主要在台式機、筆記本上使用。如Intel和AMD的CPU 。
若想在使用了基於x86結構CPU的作業系統中編譯出可以在基於arm結構CPU的作業系統上啟動並執行代碼,就必須使用交叉編譯。
交叉編譯:在一個平台下編譯出在另一個平台中可以執行的二進位代碼。Google提供的NDK就可以完成交叉編譯的工作。
NDK全稱:Native Development Kit 。
- 首先,NDK可以協助開發人員快速開發C(或C++)的動態庫。
- 其次,NDK整合了交叉編譯器。使用NDK,我們可以將要求高效能的應用邏輯使用C開發,從而提高應用程式的執行效率。
NDK工具必須在Linux下運行,它可以在linux環境下編譯出可以在arm平台下啟動並執行二進位庫檔案。
使用JNI技術,其實就是在Java程式中,調用C語言的函數庫中提供的函數,來完成一些Java語言無法完成的任務。由於Java語言和C語言結構完全不相同,因此若想讓它們二者互動,則需要制定一系列的規範。JNI就是這組規範,此時 Java只和JNI互動,而由JNI去和C語言互動。
JNI技術分為兩部分:Java端和C語言端。且以Java端為主導。
- 首先,Java程式員在Java端定義一些native方法,並將這些方法以C語言標頭檔的方式提供給C程式員。
- 然後,C程式員使用C語言,來實現Java程式員提供的標頭檔中定義的函數。
- 接著,C程式員將函數打包成一個庫檔案,並將庫檔案交給Java程式員。
- 最後,Java程式員在Java程式中匯入庫檔案,然後調用native方法。
在Java程式執行的時候,若在某個類中調用了native方法,則虛擬機器會通過JNI來轉調用庫檔案中的C語言代碼。提示:C代碼最終是在Linux進程中執行的,而不是在虛擬機器中。
--------------------------------------------------------------------------------------------------------------------------------------------
- JAVA平台和系統內容(Host Environment)
系統內容代指本地作業系統環境,它有自己的本地庫和CPU指令集。本地程式(Native Applications)使用C/C++這樣的本地語言來編寫,被編譯成只能在本地系統內容下啟動並執行二進位代碼,並和本地庫連結在一起。本地程式和本地庫一般地會依賴於一個特定的本地系統內容。比如,一個系統下編譯出來的C程式不能在另一個系統中運行。
JNI的強大特性使我們在使用JAVA平台的同時,還可以重用原來的本地代碼。作為虛擬機器實現的一部分,JNI允許JAVA和本地代碼間的雙向互動。
JNI可以這樣與本地程式進行互動:
1、 你可以使用JNI來實現“本地方法”(native methods),並在JAVA程式中調用它們。
2、 JNI支援一個“調用介面”(invocation interface),它允許你把一個JVM嵌入到本地程式中。本地程式可以連結一個實現了JVM的本地庫,然後使用“調用介面”執行JAVA語言編寫的軟體模組。例如,一個用C語言寫的瀏覽器可以在一個嵌入式JVM上面執行從網上下載下來的applets
請記住,一旦使用JNI,JAVA程式就喪失了JAVA平台的兩個優點:
1、 程式不再跨平台。要想跨平台,必須在不同的系統內容下重新編譯本地語言部分。
2、 程式不再是絕對安全的,本地代碼的不當使用可能導致整個程式崩潰。
一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就降低了JAVA和C之間的耦合性。
當你開始著手準備一個使用JNI的項目時,請確認是否還有替代方案。像上一節所提到的,應用程式使用JNI會帶來一些副作用。下面給出幾個方案,可以避免使用JNI的時候,達到與本地代碼進行互動的效果:
1、 JAVA程式和本地程式使用TCP/IP或者IPC進行互動。
2、 當用JAVA程式串連本機資料庫時,使用JDBC提供的API。
3、 JAVA程式可以使用分布式對象技術,如JAVA IDL API。
這些方案的共同點是,JAVA和C 處於不同的線程,或者不同的機器上。這樣,當本地程式崩潰時,不會影響到JAVA程式。
下面這些場合中,同一進程內JNI的使用無法避免:
1、 程式當中用到了JAVA API不提供的特殊系統內容才會有的特徵。而跨進程操作又不現實。
2、 你可能想訪問一些己有的本地庫,但又不想付出跨進程調用時的代價,如效率,記憶體,資料傳遞方面。
3、 JAVA程式當中的一部分代碼對效率要求非常高,如演算法計算,圖形渲染等。
總之,只有當你必須在同一進程中調用本地代碼時,再使用JNI。
JDK1.0包含了一個本地方法介面,它允許JAVA程式調用C/C++寫的程式。許多第三方的程式和JAVA類庫,如:java.lang,java.io,java.net等都依賴於本地方法來訪問底層系統內容的特徵。
不幸的是,JDK1.0中的本地方法有兩個主要問題:
1、 本地方法像訪問C中的結構(structures)一樣訪問對象中的欄位。儘管如此,JVM規範並沒有定義對象怎麼樣在記憶體中實現。如果一個給定的JVM實現在布局對象時,和本地方法假設的不一樣,那你就不得不重新編寫本地方法庫。
2、 因為本地方法可以保持對JVM中對象的直接指標,所以,JDK1.0中的本地方法採用了一種保守的GC策略。
JNI的誕生就是為瞭解決這兩個問題,它可以被所有平台下的JVM支援:
1、 每一個VM實現方案可以支援大量的本地代碼。
2、 開發工具作者不必處理不同的本地方法介面。
3、 最重要的是,本地代碼可以運行在不同的JVM上面。
JDK1.1中第一次支援JNI,但是,JDK1.1仍在使用老風格的本地代碼來實現JAVA的API。這種情況在JDK1.2下被徹底改變成符合標準的寫法。
----------------------------------------------------------------------------------------------------------------------------------------------
android 學習隨筆二十七(JNI:Java Native Interface,JAVA原生介面 )