您的位置:首页 > 其它

JNI原理与静态、动态注册

2017-12-01 19:51 453 查看

前言

JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层。这一个系列我们来一起深入学习JNI。

1.JNI概述

Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。

这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。



通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。

为了讲解JNI我们需要分析系统的源码,在即将出版的《Android进阶之光》的最后一章中我拿MediaPlayer框架做了举例,这里换MediaRecorder框架来举例,它和MediaPlayer框架的调用过程十分类似。

2.MediaRecorder框架概述

MediaRecorder我们应该都不陌生,它用于录音和录像。这里不会主要介绍MediaRecorder框架,而是MediaRecorder框架中的JNI。



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;
[/code]

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: