其實JNI和NDK區別可以這樣理解:JNI是一套SUN的API,而NDK更像一個工具,它是GOOGLE自己提供的,編譯C/C++的
一: 關於JNI:
JNI即 Java native intereface,為Java應用程式提供調用本地方法的介面,JNI的首要目標在以庫檔案的形式調用本地方法,在WIndows下為DLL,在UNIX下為SO。
缺陷方面:本來java程式運行在JVM上可以做到平台無關,但是如果Java程式通過JNI調用原生的代碼(C/C++等),則喪失了平台無關性。
優勢:複用C/C++代碼,調用本地庫。
注意: 在J2me上是沒有JNI,不能像JNI那樣調用外部的DLL。假如你需要使用某DLL,也只能在"設計"J2ME虛擬機器的時候靜態連結加進去,前提是你能夠弄到Symbian的JAVA虛擬機器的原始碼,然後把DLL加到虛擬機器裡,並且能夠替換掉原Symbian的JAVA虛擬機器。。(摘自CSDN某位)
二:JNI的使用
JNI程式開發的一般操作步驟如下:
1:編寫java中的調用類:設計思想:將本地方法集中在單個類中,以便將以後所需的移植工作減到最少。
2:用javah產生c/c++原生函數的標頭檔,javah 不使預設內部命令,需要指明路徑
3:c/c++中調用需要的其他函數功能,實現原生函數
4:將項目依賴的所有原生庫和資源加入到java項目的java.library.path
5:發布Java和動態庫(DLL/so放在Jar包的同一級目錄下)
三:注意事項
1:使用javah產生.h檔案時:UnsatisfiedLinkError+方法名錯誤
2:在一個Applet應用中,不要使用 JNI。因為在 applet 中可能引發安全異常。
3:將所有本地方法都封裝在單個類中,這個類調用單個 DLL。對於每種目標作業系統,都可以用特定於適當平台的版本替換這個 DLL。這樣就可以將本地代碼的影響減至最小,並有助於將以後所需的移植問題包含在內。
4:本地方法要簡單。盡量將產生的 DLL 對任何第三方運行時 DLL 的依賴減到最小。使本地方法盡量獨立,以將載入 DLL 和應用程式所需的開銷減到最小。如果必須要運行時 DLL,則應隨應用程式一起提供它們。
5:本地代碼運行時,沒有有效地防數組越界錯誤、錯誤指標引用帶來的間接錯誤等。所以必須保證保證本地代碼的穩定性,因為,絲毫的錯誤都可能導致 JAVA 虛擬機器崩潰。
四:JNI資料類型映射
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv*env, jobject obj){}
JNIEXPORT和JNICALL是在jni.h中的宏定義。確保這個函數在本地庫外可見,並且C編譯器會進行正確的調用轉換。C/C++函數的名字構成:Java+包名+類名+函數名。與javah指令產生的.h檔案的函數名對應,如果函數名不正確會導致UnsatisfiedLinkError。
JNI把JAVA中的對象當作一個C指標傳遞到本地方法中,這個指標指向JVM中的內部資料結構,而內部資料結構在記憶體中的儲存方式是不可見的。本地代碼必須通過在JNIEnv中選擇適當的JNI函數來操作JVM中的對象。
第一個參數JNIEnv介面指標,指向一個個函數表,函數表中的每一個入口指向一個JNI函數。本地方法經常通過這些函數來訪問JVM中的資料結構。
第二個參數根據本地方法是一個靜態方法還是執行個體方法而有所不同。本地方法是一個靜態方法時,第二個參數代表本地方法所在的類(jclass);本地方法是一個執行個體方法時,第二個參數代表本地方法所在的對象(jobject)。
對應基礎資料型別 (Elementary Data Type):
JNI對基本類型和參考型別的處理是不同的。基本類型的映射是一對一的。例如JAVA中的int類型直接對應C/C++中的jint。
對於參考型別:
JNI把JAVA中的對象當作一個C指標傳遞到本地方法中,這個指標指向JVM中的內部資料結構,而內部資料結構在記憶體中的儲存方式是不可見的。本地代碼必須通過在JNIEnv中選擇適當的JNI函數來操作JVM中的對象。例如String對應jstring,本地代碼只能通過GetStringUTFChars這樣的JNI函數來訪問
Java類型 |
本地類型 |
描述 |
boolean |
jboolean |
C/C++8位整型 |
byte |
jbyte |
C/C++帶符號的8位整型 |
char |
jchar |
C/C++無符號的16位整型 |
short |
jshort |
C/C++帶符號的16位整型 |
int |
jint |
C/C++帶符號的32位整型 |
long |
jlong |
C/C++帶符號的64位整型e |
float |
jfloat |
C/C++32位浮點型 |
double |
jdouble |
C/C++64位浮點型 |
Object |
jobject |
任何Java對象,或者沒有對應java類型的對象 |
Class |
jclass |
Class對象 |
String |
jstring |
字串對象 |
Object[] |
jobjectArray |
任何對象的數組 |
boolean[] |
jbooleanArray |
布爾型數組 |
byte[] |
jbyteArray |
位元型數組 |
char[] |
jcharArray |
字元型數組 |
short[] |
jshortArray |
短整型數組 |
int[] |
jintArray |
整型數組 |
long[] |
jlongArray |
長整型數組 |
float[] |
jfloatArray |
浮點型數組 |
double[] |
jdoubleArray |
雙浮點型數組 |
函數 |
Java數群組類型 |
本地類型 |
GetBooleanArrayElements |
jbooleanArray |
jboolean |
GetByteArrayElements |
jbyteArray |
jbyte |
GetCharArrayElements |
jcharArray |
jchar |
GetShortArrayElements |
jshortArray |
jshort |
GetIntArrayElements |
jintArray |
jint |
GetLongArrayElements |
jlongArray |
jlong |
GetFloatArrayElements |
jfloatArray |
jfloat |
GetDoubleArrayElements |
jdoubleArray |
jdouble |
函數 |
描述 |
GetFieldID |
得到一個執行個體的域的ID |
GetStaticFieldID |
得到一個靜態域的ID |
GetMethodID |
得到一個執行個體的方法的ID |
GetStaticMethodID |
得到一個靜態方法的ID |
Java 類型 |
符號 |
boolean |
Z |
byte |
B |
char |
C |
short |
S |
int |
I |
long |
L |
float |
F |
double |
D |
void |
V |
objects對象 |
Lfully-qualified-class-name;L類名 |
Arrays數組 |
[array-type [數群組類型 |
methods方法 |
(argument-types)return-type(參數類型)傳回型別 |
不能直接存取JNI裡面的本地類型,要通過對應函數訪問。
JNI支援字串在Unicode和UTF-8兩種編碼之間轉換。支援C/C++兩套API
//C 格式
(*env)-><jni function>( env, <parameters> )
//C++ 格式
env-><jni function>( < parameters> )
JNI API為了避免醜陋的函數名,提供了方法向Java虛擬機器註冊函數映射表。這樣當Java調用Native介面的時候,Java虛擬機器就可以不用根據函數名來決定調用哪個函數了,直接通過查詢表格就可以找到需要調用的函數了。
從本地代碼中調用Java方法。也就是通常說的來自本地方法中的callbacks(回調)。
native/java和java/native和java/java效率比較:native/java<java/native<java/java
五:JNI中的引用
JNI支援三種引用:局部引用、全域引用、弱全域引用
局部引用和全域引用有不同的生命週期。當本地方法返回時,局部引用會被自動釋放。而全域引用和弱引用必須手動釋放。
局部引用或者全域引用會阻止GC回收它們所引用的對象,而弱引用則不會。
不是所有的引用可以被用在所有的場合。例如,一個本地方法建立一個局部引用並返回後,再對這個局部引用進行訪問是非法的。
1:局部引用:
局部引用只有在建立它的本地方法返回前有效。本地方法返回後,局部引用會被自動釋放。(static局部對象也是一樣)
實際上JNI中的局部引用和C語言中局部變數是不同的,他的有效期間是當前Native函數被調用的上下文中。我理解的調用上下文,為Java虛擬機器的調用流程。Native函數是被Java虛擬機器調用的,Native函數執行完成之後,控制流程程將繼續返回給Java虛擬機器。局部變數在Native函數中,由Native代碼調用Java虛擬機器的JNI介面建立,秉著誰建立誰銷毀的原則,當Native函數執行完成之後,如果局部引用沒有被Native代碼顯示刪除,那麼局部引用在Java虛擬機器中還是有效。Java虛擬機器來決定在什麼時候來刪除這個對象。這和C語言的局部變數概念是不同的。這也可以解釋為什麼Natvie函數能夠以一個局部引用為傳回值了。
局部引用在Native代碼顯示釋放非常重要。
(如果你實現的Native函數是工具函數,會被頻繁的調用。如果你在Native函數中沒有顯示刪除局部引用,那麼每次調用該函數Java虛擬機器都會建立一個新的局部引用,造成局部引用過多。尤其是該函數在Native代碼中被頻繁調用,代碼的控制權沒有交還給Java虛擬機器,所以Java虛擬機器根本沒有機會釋放這些局部變數。)
JNIapi中有三個函數對局部引用進行處理:EnsureLocalCapacity、PushLocalFrame、PopLocalFrame
可以用棧來理解局部變數。
2:全域引用:
全域引用可以跨方法、跨線程使用,直到它被手動釋放才會失效。同局部引用一樣,全域引用也會阻止它所引用的對象被GC回收。NewGlobalRef和DeleteGlobalRef.
3:弱引用
弱引用使用NewGlobalWeakRef建立,使用DeleteGlobalWeakRef釋放。與全域引用類似,弱引用可以跨方法、線程使用。與全域引用不同的是,弱引用不會阻止GC回收它所指向的VM內部的對象。NewWeakGlobalRef和DeleteWeakGlobalRef。使用的時候應該檢查它的有效性
每一個JNI引用被建立時,除了它所指向的JVM中的對象以外,引用本身也會消耗掉一個數量的記憶體。作為一個JNI程式員,應該對程式在一個給定時間段內使用的引用數量十分小心。短時間內建立大量不會被立即回收的引用會導致記憶體溢出。
六:DLL與so相關
NDK:
一:NDK簡介
Native Development Kit。NDK提供了一系列的工具,協助開發人員快速開發C(或C++)的動態庫,並能自動將so和java應用一起打包成apk。Google提高的NDK中API支援的功能非常有限,包含有:C標準庫(libc)、標準數學庫(libm)、壓縮庫(libz)、Log庫(liblog)
二:使用cygwin
下載安裝cygwin,cygwin是cygwin是一個在windows平台上啟動並執行unix類比環境。
具體參考:百度文庫這篇:點這裡。
三:Android.mk檔案和Makefile
EOE上能找翻譯文檔。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE
:= helloworld
LOCAL_SRC_FILES := helloworld.c
include $(BUILD_SHARED_LIBRARY)