JNI概述
- Java程序中的函數可以調用Native語言寫的函數冯遂,Native一般指的是C/C++寫的函數
- Native層中的函數可以調用Java層的函數酬滤,也就是在C/C++程序中調用Java的函數
要想做到以上兩點惰聂,JNI(Java Native Interface)技術應運而生。
深入分析后可以知道,雖然Java代碼具有平臺無關性,但運行在具體平臺上的Java虛擬機并不能做到這一點蚕泽,但是JNI技術的出現屏蔽了不同平臺的差異,使得上層的Java具有了平臺無關的特性桥嗤。
MediaScanner的分析
android大量使用了JNI技術须妻,書中分析了MediaScanner這個例子。
- Java世界對應的是MediaScanner類泛领,這個類一些函數需要Native層來實現荒吏,比如對目錄的掃描。
- Native層對應是libmedia.so渊鞋,他完成了實際的功能绰更。
- JNI層對應的是libmedia_jni.so,這里可以看出JNI層其實也是Native代碼寫的锡宋。其中下劃線前的media是Native層庫的名字儡湾,這里指的就死libmedia庫,android對JNI庫的名字有規(guī)范员辩,一般是:
lib模塊名_jni.so
- Java層的MediaScanner將通過libmedia_jni.so來與Native層的libmedia.so交互盒粮。
Java層的MediaScanner
framewor/base/media/java/android/media/MediaScanner.java是MediaScanner在Java層源碼的位置鸵鸥。
public class MediaScanner
{
static {
/**加載對應的JNI庫奠滑,media_jni是對應JNI庫的名字,實際上加載動態(tài)庫的時候他會拓展成libmedia_jni.so **/
System.loadLibrary("media_jni");
native_init(); //調用native_init()函數妒穴,他是一個native函數
}
......
//掃面目錄的函數宋税,里面用到了processDirectory這個native函數
public void scanDirectories(String[] directories, String volumeName) {
......
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient);
}
......
private native void processDirectory(String path, MediaScannerClient client);
private static native final void native_init();
......
這里說明了兩點內容,在需要native函數的java類中會包括JNI的加載和Native函數的聲明讼油,指示它由JNI層去完成杰赛。
- JNI加載流程
Java層要調用Native函數,必須通過位于JNI層的動態(tài)庫才能實現矮台。動態(tài)庫的必須在運行時加載乏屯,所以,我們通常的做法就是在類的static語句中加入System.loadLibrary方法完成對動態(tài)庫的加載瘦赫,系統(tǒng)會自動將參數轉化成對應環(huán)境下真實動態(tài)庫的名字辰晕。linux上是so文件,win下就是dll文件确虱。 - Java的native函數
加載完JNI庫后含友,我們只需要在Java中聲明由關鍵字native修飾的函數即可。是不是很簡單,但是JNI層的MediaScanner就沒那么輕松了窘问。
JNI層的MediaScanner
JNI層的MediaScanner代碼位于
frameworks/base/media/jni/android_media_MediaScanner.cpp
上面再Java層聲明的native_init和processFile在這里都有具體的實現辆童,那么問題就來了,Java層聲明的函數如何才能對應到JNI層的惠赫。
- 注冊JNI函數
native_init函數位于android.media這個包中把鉴,這樣可以得到它的全路徑名稱android.media.MediaScanner.native_init,之后再將.全部換成_汉形,native_init就找到了JNI層的實現纸镊。
上述就是JNI函數注冊的問題
-
靜態(tài)注冊
流程如下:
注意:因為packagename是該類完整的包名,所以javah命令需要在包外面執(zhí)行概疆,否則會找不到該類
靜態(tài)注冊的弊端:1逗威、需要編譯所有聲明了native函數的java類。2岔冀、javah生成的jni函數名會特別長凯旭。 3、初次調用native函數時要根據函數名來搜索對應JNI層的函數使套,并建立關聯罐呼,這會影響效率。
-
動態(tài)注冊
既然Java Native函數和JNI函數一一對應侦高,能不能有這種結構可以保存這種關聯關系呢嫉柴,肯定是有的,在JNI技術中有一種JNINativeMethod的結構奉呛,它記錄下了這些關聯關系计螺。其實Android大部分采用的是動態(tài)注冊的方法
MediaScanner的JNI層中的示例:
static const JNINativeMethod gMethods[] = {
{
"processDirectory",
"(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void )android_media_MediaScanner_processDirectory
},
{
"processFile",
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void )android_media_MediaScanner_processFile
},
.......
{
"native_init",
"()V",
(void )android_media_MediaScanner_native_init
},
......
};
基本結構定義:
typedef struct {
//java類中native函數的名字,不用攜帶包名瞧壮,例如“native_init”
const char name;
//java函數的簽名信息登馒,用字符串表示,是參數類型和返回值類型的組合
const char signature咆槽;
//JNI層對應函數的函數指針陈轿,它是void 類型
void* fnPtr;
}JNINativeMethod秦忿;定義完畢之后麦射,需要執(zhí)行注冊
int register_android_media_MediaScanner(JNIEnv *env){ return AndroidRuntime::registerNativeMethods(env, kClassMediaScanner, gMethods, NELEM(gMethods)); }
我們發(fā)現AndroidRunTime類提供了注冊函數,而它又調用了JNIHelp中的jniRegisterNativeMethods方法灯谣,最終會走到JNIEnv的RegisterNatives完成注冊工作潜秋。
那到底是什么時候去執(zhí)行這個?
在Java層執(zhí)行System.loadLibrary加載JNI動態(tài)庫后酬屉,緊接著會查找該庫中一個JNI_OnLoad的函數半等,如果有的話揍愁,動態(tài)注冊就在這里完成。然而在MediaScanner對應的android_media_MediaScanner.cpp中并沒有發(fā)現這個函數杀饵。由于多媒體系統(tǒng)中很對地方用到JNI莽囤,所以register_android_media_MediaScanner這個注冊方法被放在了android_media_MediaPlayer.cpp的JNI_OnLoad方法中,當然還有其它的多媒體相關的注冊函數切距。
數據類型轉換
主要解決的問題是:在Java中調用native函數傳遞的參數是Java數據類型朽缎,如何將他們轉化成Native的數據類型。
Java分基本數據類型和引用數據類型谜悟,先看基本數據類型:
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位 |
這里要注意的是轉換成Native類型后對應的數據類型的字長话肖,char在Java中是占兩個字節(jié),jchar同樣也是葡幸,要區(qū)別于普通char只占一個字節(jié)最筒。
引用類型的轉換
Java引用類型 | Native類型 | Java引用類型 | Native類型 | |
---|---|---|---|---|
All object | jobject | char[] | jcharArray | |
java.lang.Class實例 | jclass | short[] | jshortArray | |
java.lang.String實例 | jstring | int[] | jintArray | |
Object[] | jobjectArray | long[] | jlongArray | |
boolean[] | jbooleanArray | flaot[] | jflaotArray | |
byte[] | jbyteArray | double[] | jdoubleArray | |
java.lang.Throwable實例 | jthrowable |
再拿MediaScanner舉例,其中的processFile
android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){
Java層的函數中只傳了三個參數蔚叨,即path床蜘,mineType和client,這里它們的類型都轉化成了native類型蔑水。前兩個參數分別是JNIEnv和Java層的MediaScanner對象(jobject)
JNIEnv
JNIEnv其實是一個與線程相關的代表JNI環(huán)境的結構體邢锯。
JNIEnv提供了JNI相關的系統(tǒng)函數,這些函數可以做到:調用Java函數和操作jobject對象等搀别。
- 通過JNIEnv操作jobject
在JNI規(guī)則中丹擎,用jfiledID和jMethodID來表示java類的成員變量和成員函數。比如MyMediaScannerClient中歇父。
代碼中將scanFile和handleStringTag函數的jmethodID保存為MyMediaScannerClient的成員變量蒂培,這是為了解決每次操作object都會去查詢jmethodID或jfieldID而帶來的運行效率降低。
使用可以看virtual status_t scanFile中的mEnv->CallVoidMethod
完成JNI層調用Java對象的函數庶骄。 - jstring
JNIEnv提供有關jstring的函數 - JNI簽名介紹
Java函數支持函數重載毁渗,也就是說践磅,可以定義同名但是不同參數的函數单刁,但僅僅根據函數名是沒法找到具體函數的。為了解決這個問題府适,JNI技術就將參數類型和返回值類型的組合作為一個函數的簽名信息羔飞,有了這個簽名和函數名,就能很順利的找到Java中的函數檐春。 - 垃圾回收
JNI的三種類型引用技術:
local reference:本地引用逻淌,一旦JNI函數返回時,jobject就可能會被回收
Global reference:全局引用疟暖,不主動釋放不會被回收
Weak Global reference:特殊的全局引用卡儒,可能會被回收田柔,每次使用時需要判斷是否回收 -
JNI中的異常處理
JNIEnv提供了三個函數給予幫助
ExceptionOccured函數,用來判斷是否發(fā)生異常
ExceptionClear函數骨望,用來清理JNI層發(fā)生的異常硬爆。
ThrownNew函數,用來Java層拋出異常擎鸠。