JNI原理與靜態、動態註冊

前言

JNI不單單在NDK開發中應用,它更是Android系統中Java與Native交互的橋樑,不理解JNI的話,你就只能停留在Java Framework層。這一個系列咱們來一塊兒深刻學習JNI。html

1.JNI概述

Android系統按語言來劃分的話由兩個世界組成,分別是Java世界和Native世界。那爲何要這麼劃分呢?Android系統由Java寫很差嗎?除了性能的以外,最主要的緣由就是在Java誕生以前,就有不少程序和庫都是由Native語言寫的,所以,重複利用這些Native語言編寫的庫是十分必要的,何況Native語言編寫的庫具備更好的性能。
這樣就產生了一個問題,Java世界的代碼要怎麼使用Native世界的代碼呢,這就須要一個橋樑來將它們鏈接在一塊兒,而JNI就是這個橋樑。
未命名文件(5).png
經過JNI,Java世界的代碼就能夠訪問Native世界的代碼,一樣的,Native世界的代碼也能夠訪問Java世界的代碼。
爲了講解JNI咱們須要分析系統的源碼,在即將出版的《Android進階之光》的最後一章中我拿MediaPlayer框架作了舉例,這裏換MediaRecorder框架來舉例,它和MediaPlayer框架的調用過程十分相似。java

2.MediaRecorder框架概述

MediaRecorder咱們應該都不陌生,它用於錄音和錄像。這裏不會主要介紹MediaRecorder框架,而是MediaRecorder框架中的JNI。
未命名文件(6).png
Java世界對應的是MediaRecorder.java,也就是咱們應用開發中直接調用的類。JNI層對用的是libmedia_jni.so,它是一個JNI的動態庫。Native層對應的是libmedia.so,這個動態庫完成了實際的調用的功能。android

3.Java層的MediaRecorder

咱們先來查看MediaRecorder.java的源碼,截取部分和JNI有關的部分以下所示。
frameworks/base/media/java/android/media/MediaRecorder.java數組

public class MediaRecorder{ static { System.loadLibrary("media_jni");//1 native_init();//2 } ... private static native final void native_init();//3 ... public native void start() throws IllegalStateException; ... }

在靜態代碼塊中首先調用了註釋1處的代碼,用來加載名爲「media_jni「的動態庫,也就是libmedia_jni.so。接着調用註釋2處的native_init方法,註釋3處的native_init方法用native來修飾,說明它是一個native方法,表示由JNI來實現。MediaRecorder的start方法一樣也是一個native方法。
對於Java層來講只須要加載對應的JNI庫,接着聲明native方法就能夠了,剩下的工做由JNI層來完成。app

4.JNI層的MediaRecorder

MediaRecorder的JNI層由android_media_recorder.cpp實現,native方法native_init和start的JNI層實現以下所示。
frameworks/base/media/jni/android_media_MediaRecorder.cpp框架

static void android_media_MediaRecorder_native_init(JNIEnv *env) { jclass clazz; clazz = env->FindClass("android/media/MediaRecorder"); if (clazz == NULL) { return; } ... fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.post_event == NULL) { return; } } static void android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) { ALOGV("start"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); }

android_media_MediaRecorder_native_init方法是native_init方法在JNI層的實現,android_media_MediaRecorder_start方法則是start方法在JNI層的實現。那麼,native_init方法是如何找到對應的android_media_MediaRecorder_native_init方法的呢?
這就須要瞭解JNI方法註冊的知識。jvm

5.JNI方法註冊

JNI方法註冊分爲靜態註冊和動態註冊,其中靜態註冊多用於NDK開發,而動態註冊多用於Framework開發。函數

靜態註冊

native_init方法被聲明爲註釋1處的方法,格式爲Java_包名_類名_方法名,註釋1處的方法名多了一個「l」,這是由於native_init方法有一個「_」,它會在轉換爲JNI方法時變成「_l」。
其中JNIEnv * 是一個指向所有JNI方法的指針,該指針只在建立它的線程有效,不能跨線程傳遞。
jclass是JNI的數據類型,對應Java的java.lang.Class實例。jobject一樣也是JNI的數據類型,對應於Java的Object。關於JNIEnv * 以及JNI的數據類型會在本系列的後續文章中進行介紹。post

當咱們在Java中調用native_init方法時,就會從JNI中尋找Java_com_example_MediaRecorder_native_1init方法,若是沒有就會報錯,若是找到就會爲native_init和Java_com_example_MediaRecorder_native_1init創建關聯,實際上是保存JNI的方法指針,這樣再次調用native_init方法時就會直接使用這個方法指針就能夠了。
靜態註冊就是根據方法名,將Java方法和JNI方法創建關聯,可是它有一些缺點:性能

  • JNI層的方法名稱過長。
  • 聲明Native方法的類須要用javah生成頭文件。
  • 初次調用JIN方法時須要創建關聯,影響效率。

咱們知道,靜態註冊就是Java的Native方法經過方法指針來與JNI進行關聯的,若是Native方法知道它在JNI中對應的方法指針,就能夠避免上述的缺點,這就是動態註冊。

查看上一博文http://www.cnblogs.com/CZM-/p/7943572.html

動態註冊

JNI中有一種結構用來記錄Java的Native方法和JNI方法的關聯關係,它就是JNINativeMethod,它在jni.h中被定義:

typedef struct { const char* name;//Java方法的名字 const char* signature;//Java方法的簽名信息 void* fnPtr;//JNI中對應的方法指針 } JNINativeMethod;

HardControl.c這是一個jni點燈的demo,函數名字不須要跟java類有關係省去了編譯頭文件的步驟
#if 0
 typedef struct {
    char *name;          /* Java裏調用的函數名 */
    char *signature;    /* JNI字段描述符, 用來表示Java裏調用的函數的參數和返回值類型 */
    void *fnPtr;          /* C語言實現的本地函數 */
} JNINativeMethod;
#endif

static jint fd;
#define DEVICE_NAME "/dev/leds"
jint ledOpen(JNIEnv *env, jclass cls)
{ 

    fd = open(DEVICE_NAME, O_RDWR);
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", DEVICE_NAME);
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen fd: %d", fd);
    if(fd >= 0)
        return 0;
    else
        return -1;
    return 0;
}

void ledClose(JNIEnv *env, jclass cls)
{
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose......");
    close(fd);
}

jint ledCtrl(JNIEnv *env, jclass cls,jint which, jint status)
{
    int ret = ioctl(fd, status, which);
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl: %d,  %d ret: %d",which, status, ret);

    return ret;
}

static const JNINativeMethod methods[] = {
    {"ledOpen", "()I", (void *)ledOpen},
    {"ledClose", "()V", (void *)ledClose},
    {"ledCtrl", "(II)I", (void *)ledCtrl},
};

/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass cls;

    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
        return JNI_ERR; /* JNI version not supported */
    }
    cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl");
    if (cls == NULL) {
        return JNI_ERR;
    }

    /* 2. map hello java<-->c c_hello */
    if((*env)->RegisterNatives(env, cls, methods, sizeof(methods) / sizeof(methods[0])) < 0)
        return JNI_ERR;
    
    return JNI_VERSION_1_4;
}
HardControl.java
package com.otherway.myapplication;

public class HardControl {
    public static native int ledCtrl(int which, int status);
    public static native int ledOpen();
    public static native void ledClose();


    static {
        try {
            System.loadLibrary("hardcontrol");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

上面定義了一個JNINativeMethod類型的gMethods數組,裏面存儲的就是HardControl的Native方法與JNI層方法的對應關係,其中註釋1處」ledClose」是Java層的Native方法,它對應的JNI層的方法爲ledClose。」()V」是ledClose方法的簽名信息,關於Java方法的簽名信息後續的文章會介紹。
在不一樣的類空間使用直接修改cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl");便可

相關文章
相關標籤/搜索