JNI:连接framework和native的桥梁

JNI概述

什么是JNI?

了解了Android系统架构之后,我们知道Android的系统架构中存在C++/C语言编写的Native层和Java语言编写的Framework层。首先为什么要这么划分呢?为什么不全部由Java语言来编写?除了性能的原因之外,最主要的原因就是在Java语言诞生之前,有很多的库和程序都是由Native语言来编写的,为了有更好的性能,让Java语言重复的利用这些Native语言编写的库是十分必要的。

那么问题就来了,既然产生了一个Java的世界和Native的世界,又该如何让他们相互联系相互调用呢?这就引出了我们这篇文章所要讲述的内容JNI。

JNI(Java Native Interface,Java本地接口),用于打通Java层与Native(C/C++)层,其存在于Native层,Native层虚拟机通过JNI为上层Java提供各种服务。这不是Android系统所独有的,而是Java所有。JNI是连接Java和Native的桥梁,通过JNI,Java和Native之间可以相互访问。

JNI的创建时间

在Android系统启动过程中,先启动Kernel层,然后启动init进程,由init进程fork出Zygote进程(可以横穿java和C++/C的进程)。Zygote进程启动时会调用startVM方法启动虚拟机,启动虚拟机之后就会紧接着调用startReg方法完成虚拟机中的JNI方法注册。也就是说,JNI的创建时间是Zygote进程启动虚拟机之后就创建了。

JNI的数据类型转换

在Java中如果某一个方法前面加了Native关键字,则说明其是一个Native方法,表示用JNI来实现,而在JNI层里实现的方法,其数据类型的表示与原Java的表示会有所不同,我们举一个例子:

//java代码
public class MediaRecorder{
static {
        System.loadLibrary("media_jni");//1
        native_init();//2
    }
    private static native final void native_init();//3
}

//由上可知,native_init()方法是一个Native方法,下面我们看一下JNI层中的实现

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;
    }
}

我们可以看见,在JNI的实现方法中,有一个jclass类型,它是JNI层的数据类型,Java的数据类型到了JNI层就需要转换为JNI层的数据类型。Java的数据类型分为基本数据类型和引用数据类型,在JNI层中也对这两种类型做了区分,我们下面分别看一下:

基本数据类型的转换

JavaNativeSignature
bytejbyteB
charjcharC
doublejdoubleD
floatjfloatF
intjintI
shortjshortS
longjlongJ
booleanjbooleanZ
voidvoidV

从上表可以看出,在基本数据类型的转换中,除了void以外,其他的数据类型只要在前面加上一个‘j’就可以了,第三列的Signature表示签名格式,在我们之后会介绍它用来做什么。

引用数据类型的转换

JavaNativeSignature
所有对象jobjectL+classname+;
ClassjclassLjava/lang/Class;
StringjstringLjava/lang/String;
ThrowablejthrowableLjava/lang/Throwable;
Object[]jobjectArray[L+classname+;
byte[]jbyteArray[B
char[]jcharArray[C
couble[]jdoubleArray[D
float[]jfloatArray[F
int[]jintArray[I
short[]jshortArray[S
long[]jlongArray[J
booleanjbooleanArray[Z

从上表可以看出,数组的JNI层数据类型需要以“Array”结尾,签名格式的开头都有“[’。其他的引用类型的签名格式的末尾都以”;“结尾
列举一下MediaRecorder框架的Java方法:

   private native void _setOutputFile(FileDescriptor fd, long offset, long length)
        throws IllegalStateException, IOException

_setOutputFile方法对应的JNI层的方法为:

static void
android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
  ...
}

我们可以看到,FileDescriptor类型转换为了jobject类型 ,long类型转换为了jlong类型。

JNI中的方法签名

我们知道Java中想调用Native中的方法,只需要声明一个native方法,表示由JNI层实现就可以了,剩下的工作由JNI层完成,在JNI中存在和Java代码声明的方法一一对一个的方法,那么如何来寻找他们之间一一对应的关系呢?

JNINativeMethod

在JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,他就是JNINativeMethod,它在jni.h中被定义:

typedef struct {
    const char* name;//Java方法的名字
    const char* signature;//Java方法的签名信息
    void*       fnPtr;//JNI中对应的方法指针
} JNINativeMethod;

我们看一下MediaRecorder的JNI层是如何实现的:

static const JNINativeMethod gMethods[] = {
...
    {"start",            "()V",      (void *)android_media_MediaRecorder_start},
    {"stop",             "()V",      (void *)android_media_MediaRecorder_stop},
    {"pause",            "()V",      (void *)android_media_MediaRecorder_pause},
    {"resume",           "()V",      (void *)android_media_MediaRecorder_resume},
    {"native_reset",     "()V",      (void *)android_media_MediaRecorder_native_reset},
    {"release",          "()V",      (void *)android_media_MediaRecorder_release},
    {"native_init",      "()V",      (void *)android_media_MediaRecorder_native_init},
   ...
};

在这个JNINativeMethod数组中,存放的第一个数据是Java层的Native方法名,第二个是方法对应的签名信息(各个参数的签名信息和返回值的签名信息),第三个是对应的在JNI中的方法名。而数字签名的作用就是来区分Java方法的重载,区分方法名相同而参数个数不同的情况。

举一个例子,某一个方法在Java中是这样定义的:

private native final void native_setup(Object mediarecorder_this,
        String clientName, String opPackageName) throws IllegalStateException;

他在JNI中的签名方法为:

(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V"

参照刚才给出的类型转换表格,native_setup方法的第一个参数的签名为“Ljava/lang/Object;”,后两个参数的签名为“Ljava/lang/String;”,返回值类型void 的签名为“V”,组合起来就是上面的方法签名

JNI中的JNIEnv

在刚刚我们出示的代码中,出现过JNIEnv* env,我们在这里说一下它的具体作用。

JNIEnv是一个指向全部JNI方法的指针,该指针只在创建它的线程有效,不能跨线程传递,因此不同线程的JNIEnv是彼此独立的,JNIEnv主要的作用有两点:

  • 调用Java的方法
  • 操作Java(获取Java中的变量和对象等)

我们看一下JNIEnv的定义,如下所示:

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;//C++中JNIEnv的类型 
typedef _JavaVM JavaVM; 
#else
typedef const struct JNINativeInterface* JNIEnv;//C中JNIEnv的类型  
typedef const struct JNIInvokeInterface* JavaVM;
#endif

这里使用预定义宏cplusplus来区分C和C++两种代码,如果定义了cplusplus,则是C++代码中的定义,否则就是C代码中的定义。
在这里我们也看到了JavaVM,它是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此,该进程的所有线程都可以使用这个JavaVM。通过JavaVM的AttachCurrentThread函数可以获取这个线程的JNIEnv,这样就可以在不同的线程中调用Java方法了。还要记得在使用AttachCurrentThread函数的线程退出前,务必要调用DetachCurrentThread函数来释放资源。

介绍完JNI中一些知识,接下来我们就用一个例子来介绍JNI的用法,到底是如何进行Java世界和Native世界交互的。

MediaRecorder框架

MediaRecorder我们都不陌生,它用于录音和录像,我们今天主要介绍一下该框架中的JNI。

Java世界对应的是MediaRecorder.java,也就是我们应用开发中直接调用的类。JNI层对用的是libmedia_jni.so,它是一个JNI的动态库。Native层对应的是libmedia.so,这个动态库完成了实际的调用的功能。

Java层的MediaRecorder

我们在MediaRecorder.java的源码中截取和JNI有关的部分如下所示:

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方法,表示由JNI实现。MediaRecorder的start方法同样也是一个native方法。

对于java层而言,只需要加载对应的JNI库,声明native方法就行了,剩下的工作由JNI层完成。

JNI层的MediaRecorder

MediaRecorder的JNI层由android_media_recorder.cpp实现。

native方法native_init和start的JNI层实现如下所示:

native_init

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;
    clazz = env->FindClass("android/media/MediaRecorder");//1
    if (clazz == NULL) {
        return;
    }
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");//2
    if (fields.context == NULL) {
        return;
    }
    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");//3
    if (fields.surface == NULL) {
        return;
    }
    jclass surface = env->FindClass("android/view/Surface");
    if (surface == NULL) {
        return;
    }
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");//4
    if (fields.post_event == NULL) {
        return;
    }
}

struct fields_t {
    jfieldID    context;
    jfieldID    surface;
    jmethodID   post_event;
};
static fields_t fields;

了解了上面的知识,我们可以轻松的解读这段代码。

在注释1处,通过env的FindClass方法来找到Java层的MediaRecorder的class对象并赋值给clazz变量,因此clazz就是Java层的MediaRecorder在JNI层的代表。注释2和注释3是调用GetFieldID方法来找到Java层的MediaRecorder类中的mNativeContext和mSurface变量,并赋值给context和surface。注释4调用GetStaticMethodID方法获取Java层该类中名为postEventFromNative的静态方法,并赋值给post_event。

在代码中也给出了fields的定义,我们将Java层的变量和方法赋值给fields中的jfieldID和jmethodID是为了效率,所以在初始化方法init中就将这些变量保存起来,方便后续使用。

以上代码看出native_init做了初始化操作,获取了上下文和一些方法,可知JNI层可以调用Java层的代码,换句话说就是Native层是可以通过JNI层来调用java层代码的。

start()

static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
    ALOGV("start");
    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);//1
    process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");//2
}

在这个JNI层的start方法中,在注释1处使用getMediaRecorder方法Native层MediaRecorder的强指针,然后再注释2处通过process_media_recorder_call方法,调用了mr->start,也就是调用了Native层的start方法。这就说明JNI层可以调用Native层的代码,换句话说也就是Java层可以通过JNI层来调用Native层代码。

可能有一个疑问,注释1是如何获取到Native层的强指针的呢?我们看一下下面的代码:

//JNI层
static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz)
{
    Mutex::Autolock l(sLock);
    MediaRecorder* const p = (MediaRecorder*)env->GetLongField(thiz, fields.context);
    return sp<MediaRecorder>(p);
}

我们看到在getMediaRecorder方法中,我们调用了GetLongField方法获取了Java层的context变量,我们再来看一下Java层中的这个变量是什么?

// The two fields below are accessed by native methods
@SuppressWarnings("unused")
private long mNativeContext;

@SuppressWarnings("unused")
@UnsupportedAppUsage
private Surface mSurface;

通过注释可以看出上面显示的两个字段由Native访问,而mNativeContext成员就保存了native层c++的MediaRecorder对象的指针。

所以通过getMediaRecorder方法是可以获取到Native层MediaRecorder的强指针。

小结

通过对上面两个处于JNI层方法的描述,我们已经知道JNI层可以访问调用Java层和Native层的代码,JNI真正起到了一个桥梁的作用,通过JNINativeMethod间接实现了Java世界和Native世界的关联。

JNI方法注册

刚才我们在说JNINativeMethod,可以通过这个数组来进行Java方法和Native方法的一一对应查找,但是并不是所有的JNI都有JNINativeMethod,只有动态注册才会有这个数组。而JNI方法注册分为两种,一种是静态注册,一种是动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。

静态注册

在AS中新建一个Java Library名为media,这里仿照系统的MediaRecorder.java,写一个简单的MediaRecorder.java,如下所示。

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

接着进入项目的media/src/main/java目录中执行如下命令:

javac com.example.MediaRecorder.java
javah com.example.MediaRecorder

第二个命令会在当前目录中(media/src/main/java)生成com_example_MediaRecorder.h文件,如下所示。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_MediaRecorder */

#ifndef _Included_com_example_MediaRecorder
#define _Included_com_example_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init(JNIEnv *, jclass);//1
/*
 * Class:     com_example_MediaRecorder
 * Method:    start
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_start(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

native_init方法被声明为注释1处的方法,格式为Java的包名类名方法名,注释1处的方法名多了一个“l”,这是因为native_init方法有一个下划线,它会在转换为JNI方法时变成“_l”。

当我们在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中对应的方法指针,就可以避免上述的缺点,这就是动态注册。

动态注册

我们刚说过的MediaRecorder框架采用的就是动态注册,在jni.h中就会定义JNINativeMethod:

typedef struct {
    const char* name;//Java方法的名字
    const char* signature;//Java方法的签名信息
    void*       fnPtr;//JNI中对应的方法指针
} JNINativeMethod;

我们看一下MediaRecorder框架JNI层如何做的:

static const JNINativeMethod gMethods[] = {
...
    {"start",            "()V",      (void *)android_media_MediaRecorder_start},//1
    {"stop",             "()V",      (void *)android_media_MediaRecorder_stop},
    {"pause",            "()V",      (void *)android_media_MediaRecorder_pause},
    {"resume",           "()V",      (void *)android_media_MediaRecorder_resume},
    {"native_reset",     "()V",      (void *)android_media_MediaRecorder_native_reset},
    {"release",          "()V",      (void *)android_media_MediaRecorder_release},
    {"native_init",      "()V",      (void *)android_media_MediaRecorder_native_init},
   ...
};

在上面直接定义了一个JNINativeMethod类型的gMethods数组,里面存储的就是MediaRecorder的Native方法与JNI层方法的对应关系,其中注释1处”start”是Java层的Native方法,它对应的JNI层的方法为android_media_MediaRecorder_start。”()V”是start方法的签名信息。

然而定义完这个数组之后,还要对他进行注册,调用register_android_media_MediaRecorder方法:

//JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}

继续调用AndroidRuntime的registerNativeMethods方法:

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

然后调用jniRegisterNativeMethods方法:

extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
   ...
    if (env->RegisterNatives(c.get(), gMethods, numMethods) < 0) {//1
        char* msg;
        (void)asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        env->FatalError(msg);
    }
    return 0;
}

由注释1可以看出,最终是调用的env->RegisterNatives方法进行注册的。

那么系统什么时候调用的register_android_media_MediaRecorder方法开始动态注册呢?答案是在Syatem.loadLibrary方法装载库文件之后调用的JNI_OnLoad方法中调用的,因为多媒体框架中的很多框架都要进行JNINativeMethod数组注册,因此,注册方法就被统一定义在JNI_OnLoad方法中,如下:

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    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_MediaPlayer(env) < 0) {			//1
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto *bail;
    }
    if (register_android_media_MediaRecorder(env) < 0) {		//2
        ALOGE("ERROR: MediaRecorder native registration failed\n");
        goto *bail;
    }
  ...
   result = JNI_VERSION_1_4;
bail:
    return result;
}

如上,注释1为注册MediaPlayer,注释2为注册MediaRecorder。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值