JNI原理與靜態、動態注冊


前言

JNI不僅僅在NDK開發中應用,它更是Android系統中Java與Native交互的橋梁,不理解JNI的話,你就只能停留在Java Framework層。這一個系列我們來一起深入學習JNI。

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框架的調用過程十分類似。

2.MediaRecorder框架概述

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

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層來完成。

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方法注冊的知識。

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的數據類型會在本系列的后續文章中進行介紹。

當我們在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");即可


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM