在Android開發中會遇到使用JNI的情況,JNI是Java Native Interface的縮寫,即Java本地接口,通過JNI技術可以實現兩點:
1)Java程序能夠調用Native函數,Native一般指的是C/C++
2)Native函數能夠調用Java層的方法
1.JNI註冊JNI的註冊分成兩種:1)靜態註冊;2)動態註冊
Java層實現調用Native函數的代碼示例如下:
public class JniHelper
{
static {
System.loadLibrary("jni_helper");
}
private static native void nativeFunc();
}
其中jni_helper是jni層庫libjni_helper.so去掉lib後的名字,是由程序運行時系統加載,帶有native關鍵字的方法,說明此方法是native代碼實現的,也就是說該方法在庫libjni_helper.so中實現
所謂的註冊,也就是將java層中的nativeFunc()對應到libjni_helper.so庫中的實現上,將聲明和實現聯繫起來
1)靜態註冊的流程一般是,先編寫java層代碼,將java代碼編譯成class文件,再使用java提供的javah工具生成一個JNI層頭文件,例如:javah -o jni_helper packgename.JniHelper,packgename.JniHelper是class文件,生成一個名爲jni_helper.h的頭文件,其中Java層聲明的nativefunc()方法在JNI頭文件中會聲明爲JNIEXPORT void JNICALL Java_pack_age_name_JniHelper_nativeFunc(JNIEnv*, jclass);
之所以將packagename寫成pack_age_name,是因爲包名中的"."會轉換成"_",此處僅作示例
最後,根據這個生成的JNI層頭文件,對應實現native方法就可以了,在Java層調用Native函數的時候,系統會到JNI庫libjni_helper.so中去找對應的方法,找到的話就會爲Java層方法和庫中的方法(即JNI頭文件中那一長串的方法聲明)建立起關聯,這就是靜態註冊的方式
2)實際上android中大都用的是動態註冊方式,這種方式下會用到一個結構體JNINativeMethod,在JNI層代碼中會定義一個JNINativeMethod數組
static JNINativeMethod gMethod[] = {
{
"nativeFunc", //Java層方法聲明
"()V", //函數簽名
(void *)nativeFFFunc //JNI層對應的函數指針
}
};
對應的註冊函數如下:
int registerNativeMethod(JNIEnv * env, const char * className, const JNINativeMethod * gMethod, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if((*env)->RegisterNatives(env, clazz, gMethods, numMethods)<0)
{
return -1;
}
return 0;
}
調用JNIEnv的RegisterNatives函數,就能夠動態註冊這種Java層函數與Native函數的關聯
最後還需要實現JNI_Onload函數,該函數在JNI庫libjni_helper.so加載時會去調用,如果實現了該函數,則調用它,沒有實現則略過,當然在靜態註冊的時候也可以實現該函數,完成一些初始化工作,JNI_Onload函數示例如下:
jint JNI_Onload(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if(vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK)
{
return result;
}
//動態註冊JNI函數,className指明Java中的類,形如"com/example/JniHelper",包的"."由"/"代替
registerNativeMethod(JNIEnv* env, const char* className, const JNINativeMethod* gMethod, int numMethods);
return JNI_VERSION_1_4; //這個必須有,否則會報錯
}
2.數據類型轉換Java數據類型分爲基本數據類型和引用數據類型兩種
1)基本數據類型轉換如下:
Java | Native | 符號 | 位數 |
---|---|---|---|
boolean | jboolean | 無符號 | 8位 |
byte | jbyte | 無符號 | 8位 |
char | jchar | 無符號 | 16位 |
short | jshort | 有符號 | 16位 |
int | jint | 有符號 | 32位 |
long | jlong | 有符號 | 64位 |
float | jfloat | 有符號 | 32位 |
double | jdouble | 有符號 | 64位 |
2)引用數據類型轉換如下:
Java | Native |
---|---|
java.lang.Class | jclass |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
其他的Object | jobject |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
3.JNI類型簽名
因爲Java有函數重載的概念,所以單靠函數名不能區分函數,需要有簽名,簽名由參數類型和返回值組成,簽名格式:
(參數1類型標識參數2類型標識......參數n類型標識)返回值類型標識
類型標識表:
Java類型 | 類型標識 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
String | L/java/langaugeString; |
int[] | [I |
Object[] | [L/java/lang/object; |
String f() 的簽名是()Ljava/lang/String;
int f(char c, int i)的簽名是(CI)I
簽名寫起來麻煩,看起來也彆扭,不過java有一個javap工具能幫我們生成簽名信息,用法如:javap -s -p classfile, classfile是編譯後的class文件,-s輸出內部數據類型的簽名信息,-p打印所有函數和成員的簽名信息