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");便可

相关文章
相关标签/搜索