1. 程式人生 > >JNI(深入理解Android卷I)的讀書筆記

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方法的宣告
    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、processDirectory等這些函式由JNI層實現。

其中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