編輯:Android游戲開發
NDK是Native Developement Kit的縮寫,顧名思義,NDK是Google提供的一套原生Java代碼與本地C/C++代碼“交互”的開發工具集。而Android是運行在Dalvik虛擬機之上,支持通過JNI的方式調用本地C/C++動態鏈接庫。C/C++有著較高的性能和移植性,通過這種調用機制就可以實現多平台開發、多語言混編的Android應用了。當然,這些都是基於JNI實現的。在游戲開發中,這種需求更是必不可少。
1、認識JNI
JNI是Java Native Interface的縮寫,也稱為Java本地接口。是JVM規范中的一部分,因此,我們可以將任何實現了JVM規范的JNI程序在Java虛擬機中運行。這裡的本地接口,主要指的是C/C++所現實的接口。因此,也使得我們可以通過這種方式重用C/C++開發的代碼或模塊。
具體關於JNI的詳細介紹,可以參見JNI的官方文檔。
Java Native Interface Specification:
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
2、JNI的類型和數據結構
實現原生Java代碼與本地C/C++代碼,一個重要的環節是將原生Java的類型和數據結構映射成本地C/C++支持的相應的類型和數據結構。
(1)Java基本數據類型與原生C/C++類型對應關系如下:
Java類型 本地類型 說明
boolean jboolean 無符號,8位
byte jbyte 無符號,8位
char jchar 無符號,16位
short jshort 有符號,16位
int jint 有符號,32位
long jlong 有符號,64位
float jfloat 32位
double jdouble 64位
void void N/A
(2)Java引用數據類型與原生C/C++類型對應關系如下:
Java類型 本地類型
Object jobject
Class jclass
String jstring
Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
通過上面的對應關系可以發現,本地類型的命名基本上是在Java原生類型明明的前面加上了個j,組成j-type格式的新類型命名,還是很直觀的。
(3)JNI引用類型的類關系圖,如下:
(上圖源自:Java Native Interface Specification文檔)
3、JNI函數的簽名
在函數的聲明中,由函數的參數,返回值類型共同構成了函數的簽名。因此,將Java函數映射到本地C/C++中的對應也要遵循相應的規則。
(1)函數數據類型的簽名關系如下:
Java類型 類型簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
full-qualified-class(全限定的類) L
[] [
boolean[] [Z
byte[] [B
char[] [C
short[] [S
int[] [I
long[] [J
float[] [F
double[] [D
注意:
1. full-qualified-class(全限定的類):指的是引用類型,用L加全類名表示。
2. 數組類型的簽名,只取中括號左半邊。
(2)JNI函數簽名格式比較
Java函數原型:
return-value fun(params1, params2, params3)
return-value:表示返回值
params:表示參數
對應函數簽名格式為:
(params1params2params3)return-value
注意:
1. JNI函數簽名中間都沒逗號,沒有空格
2. 返回值在()後面
3. 如果參數是引用類型,那麼參數應該寫為:L加全類名加分號。例如:Ljava/lang/String;
根據這種規則,知道Java函數原型就能判斷出對應的JNI函數的簽名格式:
Java代碼4、JNI開發流程
1.簡要開發步驟
JNI的具體開發流程總結起來分為這麼幾步:
(1)在原生java類中聲明native方法。native表明該方法為一個本地方法,由C/C++實現。
(2)使用javac命令將帶有聲明native方法的類,編譯成class字節碼。javac是jdk自帶的一個命令,一般在javapath/bin(javapath為java安裝目錄)路徑下。
(3)使用javah命令將編譯好的class生成本地C/C++代碼的.h頭文件。同樣,javah也是jdk自帶的一個命令。
(4)實現.h頭文件中的方法。
(5)將本地代碼編譯成動態庫。注意,不同平台的動態庫是不一樣的。
(6)在java工程中引用編譯好的動態庫。
2.開發實例
按照上面的開發步驟作為指導,來一步步實現個簡單的JNI的例子。
(1)新建名為HelloJNI的java工程,並新建一個聲明了native方法的類。(這裡就以Eclipse作為開發IDE舉例了)
Java代碼(2)使用javac編譯該HelloJni的類編譯為.class文件。當然,這步也可以由Eclipse來完成即可。
(3)使用javah命令生成頭文件。在命令行終端下輸入如下命令:
javah -classpath E:\workplace\java\HelloJNI\src com.hellojni.test.HelloJni
classpath:是指定加載類的路徑
com.hellojni.test.HelloJni:為完整類名。注意,不需要帶java
具體javah的使用參數介紹,可以輸入javah -help。
如果,執行成功,會在當前目錄下生成com_hellojni_test_HelloJni.h的頭文件。
C++代碼可以看到javah自動為我們生成了一個Java_com_hellojni_test_HelloJni_printJni的方法。格式是:Java_Packagename_Classname_Methodname。
首先,這裡引入了jni.h的頭文件。這個是jdk自帶的一個頭文件,一般在javapath/include(javapath為java安裝目錄)。
(4)打開VS新建一個Win32控制台應用程序,應用程序類型選擇DLL(Win平台動態庫為.dll)。並將生成的Java_com_hellojni_test_HelloJni_printJni.h頭文件拷貝到該工程目錄下。
然後,再將該頭文件添加到工程中。如圖:
編譯生成一下。會提示找不到jni.h。因此,把jni.h拷貝到工程目錄下,並加入到項目中。jni.h一般在javapath/include(javapath為java安裝目錄)路徑下。
重新編譯生成下,會提示找不到jni_md.h。這個文件在,javapath/include/win32路徑下。拷貝該文件再加入工程。並修改Java_com_hellojni_test_HelloJni_printJni.h頭文件。
將#include 修改為#include "jni.h",在當前目錄下找jni.h頭文件。
新建一個hellojni.cpp的源文件。如下:
C++代碼(5)再將工程重新生成下,成功的話,會在工程的Debug目錄下生成一個HelloJni.dll的動態庫。將HelloJni.dll所在的路徑添加到環境變量,這樣每次重新生成,在任意目錄都能訪問。
(6)在java工程中引用剛生成的HelloJni.dll。並加入如下代碼:
Java代碼調用System.loadLibrary來加載動態庫。注意,動態庫的名字不需要加.dll。
運行java工程,這時候會提示Exception in thread “main” java.lang.UnsatisfiedLinkError: no HelloJni in java.library.path。這時候,需要重啟下Eclipse。因為,剛配置的環境變量。重啟下,Eclipse才能識別。
重啟完畢,運行java工程。控制台會輸入:
Hello JNI
表明整個JNI調用成功。
第一篇就介紹這麼多,大體明白了JNI的整個開發流程及基本規則。下一篇將介紹下在Android NDK環境下的交叉編譯及調用過程。