一、JNI(Java Native Interface, Java本地調用)
作用:在Java程序中的函數可以調用C/C++編寫的函數;
在C/C++程序中的函數可以調用Java編寫的函數;
JNI實例
Java(MediaScanner)<——>JNI(libmedia_jni.so)<——>Native(libmedia.so)
在Java層中的MediaScanner類中有一些函數需要由Native層來實現;
MediaScanner將通過JNI庫libmedia_jni.so和Native層的libmedia.so交互;
從上面的分析可知,JNI層必須實現爲動態庫的形式,這樣Java虛擬機才能加載和調用 它的函數;
Java層的MediaScanner分析
MediaScanner.java
public class MediaScanner
{
static {
/*1)加載對應的JNI庫,media_jni是JNI庫的名字,在實際加載動態庫的時候將其拓展成libmedia_jni.so,windows平臺上拓展爲media_jni.dll*/
System.loadLibrary("media_jni");
native_init(); //調用native_init函數
}
......
//2)聲明一個native函數,native爲Java關鍵字,表示它將由JNI層完成。
private static native final void native_init();
......
private native void processFile(String path, String mimeType, MediaScannerClient client);
......
}
上面的代碼列出了兩個要點:一個是加載JNI庫,另一個是Java的native函數。
加載JNI庫
原則上在調用native函數前,都可以加載;通常在類的static語句中加載;
調用System.loadLibrary()方法;
從上面的代碼可以發現,native_init和processFile函數前都有Java的關鍵字native,它表示這兩個函數將由JNI層來實現;
JNI層MediaScanner的分析
MediaScanner的JNI層代碼在android_media_MediaScanner.cpp中
android_media_MediaScanner.cpp
//native_init的JNI實現
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
......
}
//processFile的JNI層實現
static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
{
......
}
Java層native_init函數如何對應JNI層的android_media_MediaScanner_native_init函數?
註冊JNI函數
native_init函數位於android.media這個包中,它的全路徑名應該是android.media.MediaScanner.native_init,而JNI層函數的名字是android_media_MediaScanner_native_init;
JNI層中把Java函數名稱中的“.”換成“_”,通過這種方式native_init找到JNI層的對應函數android_media_MediaScanner_native_init;
1、靜態方法
使用Java的工具程序javah找對應的JNI函數,流程如下:
先編寫Java代碼,然後編譯生成.class文件。
使用Java工具程序javah,$ javah -o output packagename.classname
之後生成output.h的JNI層頭文件,其中packagename.classname是Java代碼編譯後的class文件,在output.h中聲明瞭對應的JNI層函數,只要實現裏面的函數即可。
如MediaScanner對應JNI層頭文件就是android_media_MediaScanner.h
對應關係:
當Java層調用native_init函數時,它會從對應的JNI庫中尋找Java_android_media_MediaScanner_native_init函數,如果沒有就報錯。如果找到就爲這兩個函數建立一個關聯關係,就是保存JNI層函數的函數指針,以後再調用就可以使用這個函數指針了,這些工作是由虛擬機完成的。
2、動態註冊
JNI中使用JNINativeMethod結構保存上面Java和JNI層函數的一一對應的關係;
//定義一個JNINativeMethod數組,其成員就是Java層中所有的native函數的一一對應關係
static JNINativeMethod gMethods[] = {
......
{
"processFile" //Java中native函數的函數名
//processFile的簽名信息,簽名信息由函數參數及返回值構成;
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void *)android_media_MediaScanner_processFile //JNI層對應的函數指針
},
......
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
};
//註冊JNINativeMethod數組
int register_android_media_MediaScanner(JNIEnv *env)
{
//調用AndroidRuntime的registerNativeMethods函數,第二個參數表明是Java中的哪個類
return AndroidRuntime::registerNativeMethods(env, "andorid/media/MediaScanner", gMethods, NELEM(gMethods));
}
AndroidRunTime提供了一個registerNativeMethods函數來完成註冊工作,下面看registerNativeMethods的實現,代碼如下:
int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethod, int numMethods)
{
//調用jniRegisterNativeMethods函數完成註冊
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
其中jniRegisterNativeMethods是Android平臺爲了方便JNI使用而提供的一個幫助函數,代碼如下:
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
......
//實際上是調用JNIEnv的RegisterNatives函數完成註冊的
if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return -1;
}
return 0;
}
動態註冊的工作只用兩個函數就能完成。
jclass clazz = (*env)->FindClass(env, className);
(*env)->RegisterNatives(env, clazz, gMethods, numMethods);
動態註冊的函數,當Java層通過System.loadLibrary加載完JNI動態庫後,緊接着會查找該庫中一個叫JNI_OnLoad的函數,調用它完成註冊;
建議實現這個JNI_OnLoad函數,在其中做一些初始化的工作;
函數簽名信息的生成
java提供了一個javap的工具可以幫助生成函數和變量的簽名信息;
$ java -s -p xxx
其中xxx爲編譯生成的class文件,s表示輸出內部數據類型的簽名信息,p表示打印所有函數和成員的簽名信息,默認只會答應public的成員和函數的簽名信息;
二、XPCOM組件