JNI(深入理解Android卷I)的讀書筆記
一:概述
JNI:Java Native Interface。
作用:連線Java世界和Native世界。Java程式中函式可以呼叫Native語言寫的函式;Native程式中的函式可以呼叫Java層的函式。
二:例項:MediaScanner
2.1 關係:
Java層(MediaScanner)<---------->JNI層(libmedia_jni.so)<--------->Native層(libmedia.so)
JNI庫名規則:lib模組名_jni.so 如上:media_jni就是庫名
JNI 層必須實現為動態庫的形式。
MediaScanner類中有一些函式需要由Native層來實現。
2.2 Java層MediaScanner分析
開啟MediaScanner.java原始碼,\frameworks\base\media\java\android\media
<pre name="code" class="java">package android.media;
…… public class MediaScanner { static { System.loadLibrary("media_jni"); //載入media_jin庫,載入時會拓展成libmedia_jni.so(linux平臺)或libmedia_jni.dll(windows平臺) native_init(); } ……//一些非native方法的定義 public void scanDirectories(String[] directories, String volumeName) {//非native函式 ……
}
上面程式碼中的native_init、processDirectory等這些函式由JNI層實現。……//一些native方法的宣告 private native void processDirectory(String path, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client); public native void setLocale(String locale); public native void setMediaflag(int mediaflag); public native byte[] extractAlbumArt(FileDescriptor fd); private static native final void native_init(); private native final void native_setup(); private native final void native_finalize(); …… }
其中native_init() 的全路徑是:android.media.MediaScanner.native_init;
processDirectory 函式的全路徑是:android.media.MediaScanner.processDirectory
將“.”換成“_"就是JNI對應函式的名字。
2.3 JNI層MediaScanner分析
開啟原始碼android_media_MediaScanner.cpp,frameworks\base\media\jni
#include "jni.h"
#include "JNIHelp.h"
// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
if (fields.context == NULL) {
return;
}
}
static void
android_media_MediaScanner_processDirectory(
JNIEnv* env, jobject thiz, jstring path, jobject client){……}
需要註冊JNI函式,在java層呼叫native函式時,才能順利轉到JNI層對應函式執行。
註冊有兩種方法:
1:靜態註冊:根據檔名確立Java函式和JNI函式的關聯關係。再通過函式指標操作。有弊端:效率低,JNI函式名長……
2:動態註冊:利用結構體JNINativeMethod儲存某個Java native函式和對應的JNI 函式的關聯關係。
多對關聯關係通過JNINativeMethod型別陣列來儲存。
下面詳細介紹動態註冊:
註冊過程的實現:
1:JNINativeMethod結構體
開啟jni.h ,libnativehelper\include\nativehelper
typedef struct {
const char* name; //Java中函式的名字
const char* signature; //簽名信息即用字串描述的函式的引數和返回值
void* fnPtr; //指向C函式的函式指標
} JNINativeMethod;
2:構建JNINativeMethod型別陣列來表示多對Java native函式和對應的JNI 函式的關聯關係。
開啟原始碼android_media_MediaScanner.cpp,frameworks\base\media\jni
static 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
},
{
"setLocale",
"(Ljava/lang/String;)V",
(void *)android_media_MediaScanner_setLocale
},
{
"setMediaflag",
"(I)V",
(void *)android_media_MediaScanner_setMediaflag
},
{
"extractAlbumArt",
"(Ljava/io/FileDescriptor;)[B",
(void *)android_media_MediaScanner_extractAlbumArt
},
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
{
"native_setup",
"()V",
(void *)android_media_MediaScanner_native_setup
},
{
"native_finalize",
"()V",
(void *)android_media_MediaScanner_native_finalize
},
};
上述陣列成員與MediaScanner.java中宣告的native方法以及android_media_MediaScanner.cpp中的方法是一一對應的。
觀察陣列成員中的簽名信息,如
"(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
簽名信息:由引數型別和返回值共同組成。(因為java支援函式過載,所以必須藉助返回值型別和引數型別來定位函式)
對應於Java端的資料型別,我們也可以看一下下面的表:
Java 型別 | 型別簽名 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
類 | L全限定名;,比如String, 其簽名為Ljava/lang/util/String; |
陣列 | [型別簽名, 比如 [B |
3:註冊上述陣列
開啟原始碼android_media_MediaScanner.cpp,frameworks\base\media\jni
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));//傳入被註冊陣列
}
通過呼叫registerNativerMethods函式完成註冊。registerNativerMethods函式是在AndroidRunTime.cpp中定義的,AndroidRunTime.cpp也是位於JNI層的。
開啟AndroidRunTime.cpp ,路徑frameworks\base\core\jni
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
jniRegisterNativeMethods
再通過呼叫jniRegisterNativerMethods函式完成註冊,jniRegisterNativerMethods函式是Android平臺提供的幫助函式。
開啟JNIHelp.cpp ,路徑:\libnativehelper
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env,const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv*e = reinterpret_cast<JNIEnv*>(env);
ALOGV("Registering %s's %d native methods...", className, numMethods);
scoped_local_ref<jclass> c(env, findClass(env, className));
if (c.get() == NULL) {
char* msg;
asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
e->FatalError(msg);
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
char* msg;
asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
e->FatalError(msg);
}
return 0;
}
再通過JNIEvn的RegisterNatives函式完成註冊。JNIEvn是一個重要的結構體。
總結:
JNINativeMathod中使用的是函式名而不是全名,所以需要指明是哪個類。最終是呼叫JNIEvn的RegisterNatives函式完成註冊。
註冊的時機:
當Java層呼叫System.loadLibrrary載入完JNI動態庫後,會查詢該庫中的JNI_OnLoad函式,如果有則呼叫,完成註冊。
下面看JNI_OnLoad函式的實現:
開啟原始碼android_media_MediaPlayer.cpp,frameworks\base\media\jni
jint JNI_OnLoad(JavaVM* vm, void* reserved)//JavaVM是虛擬機器在JNI層的代表
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
……
if (register_android_media_MediaScanner(env)< 0) { //動態註冊MediaScanner的JNI函式
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
if (register_android_media_MediaMetadataRetriever(env) < 0) {
ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
}
……
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
2.4 資料型別轉換
java的資料型別與native語言的資料型別是有一個轉換規則的。
在JNI中,提供了以下各種資料型別,可以分為原生型別和引用型別: 對於原生型別有:jchar, jbyte, jshort, jint, jlong, jfloat, jdouble,
jboolean,其與java端的資料型別對應如下表:
java | jni |
char | jchar |
byte | jbyte |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
boolean | jboolean |
對於引用型別則有:jobject, jstring, jthrowable, jclass, jarray, 以及繼承於jarray,對應於其原生型別的8種jarray和jobjectarray。
2.5 介紹上面中出現過的JNIEnv結構體
從呼叫JNI_OnLoad函式開始註冊起,在函式的不斷呼叫過程中一直伴隨著這個結構體型別的變數。所以下面開始介紹它:
這個一個與執行緒相關的代表JNI環境的結構體。首先看JNIEnv的體系結構:
JNIEnv首先指向一個執行緒相關的結構,該結構又指向一個指標陣列,在這個指標陣列中的每個元素最終指向一個JNI函式。所以可以通過JNIEnv去呼叫JNI函式。
開啟jni.h ,libnativehelper\include\nativehelper
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
在jni.h中,為了相容C和C++兩種程式碼,使用巨集__cplusplus加以區分。關於_JNIEvn的定義可以繼續看程式碼,最終的實現時基於虛擬機器的。
JNIEnv只在當前執行緒中有效。本地方法不能將JNIEnv從一個執行緒傳遞到另一個執行緒中。相同的 Java 執行緒中對本地方法多次呼叫時,傳遞給該本地方法的JNIEn是相同的。但是,一個本地方法可被不同的 Java 執行緒所呼叫,因此可以接受不同的 JNIEnv。http://book.51cto.com/art/201305/395882.htm
JNIEvn的作用:呼叫Java的函式;操作jobject物件……(怎麼實現這些功能的呢,主要是通過函式獲取jobject的成員變數和成員函式,再呼叫不同函式控制。PS:個人覺得有點象java中通過反射控制物件。)
2.6 JNI中的垃圾回收
JNI技術提供了三種類型引用:
Local Reference
Global Reference
Weak Global Reference
2.7 JNI異常
如果呼叫JNIEvn的某些函數出錯了,會產生異常,但是不會中斷本地函式的執行,直到從JNI返回到JAVA層才會丟擲這個異常。可能會導致程式死掉。
相關函式:
ExceptionOccured
ExceptionClear
ThrowNew