01-深入理解JNI

01-深入理解JNI,第1张

目录

1 JNI概述

 2 学习JNI的实例:MediaPlayer

2.1 加载JNI库

2.2 JNI层MediaPlayer的分析

2.3 注册JNI函数

2.3.1 静态注册

2.3.2 动态注册


1 JNI概述

        JNI是Java Native Interface的缩写,中文译为“Java本地调用”。


通俗地说,JNI是一种技术,通过这种技术可以做到以下两点:

        a). Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数


        b). Native程序中的函数可以调用Java层的函数,也就是说在C/C++程序中可以调用Java的函数。


        也就是Java与C/C++代码互调

        在平台无关的Java中,为什么要创建一个与Native相关的JNI技术呢?这岂不是破坏了Java的平台无关特性吗?JNI技术的推出有以下几个方面的考虑:

        承载Java世界的虚拟机是用Native语言写的,而虚拟机又运行在具体的平台上,所以虚拟机本身无法做到平台无关。


然而,有了JNI技术后就可以对Java层屏蔽不同 *** 作系统平台(如Windows和Linux)之间的差异了(例如同样是打开一个文件,Windows上的API使用OpenFile函数,而Linux上的API是open函数)。


这样,就能实现Java本身的平台无关特性。


其实Java一直在使用JNI技术,只是我们平时较少用到罢了。


        早在Java语言诞生前,很多程序都是用C/C++语言写的,它们遍布在软件世界的各个角落。


Java出世后,它受到了追捧,并迅速得到发展,但仍无法将软件世界彻底改朝换代,于是才有了折中的办法。


既然已经有C/C++模块实现了相关功能,那么在Java中通过JNI技术直接使用它们就行了,免得落下重复制造轮子的坏名声。


另外,在一些要求效率和速度的场合还是需要C/C++语言参与的。


        在Android平台上,JNI就是一座将C/C++世界和Java世界间的天堑变成通途的桥。


下图展示了Android平台上JNI所处的位置:

 2 学习JNI的实例:MediaPlayer

        初次接触JNI,感觉最神奇的就是Java竟然能够调用Native的函数,可它是怎么做到的呢?由于Android大量使用了JNI技术,本章接下来的内容就将通过源码中的一处实例来讲解JNI相关的知识。


        其中这个例子是和MediaPlayer相关的,本书的最后一章会详细分析它的工作原理,这里先看与JNI相关的部分,如下图所示:

         结合来看,可以知道:

        Java世界对应的是MediaPlayer,而这个MediaPlayer类有一些函数需要由Native层来实现。


JNI层对应的是libmedia_jni.so。


media_jni是JNI库的名字,其中,下划线前的“media”是Native层库的名字,这里就是libmedia库。


下划线后的“jni”表示它是一个JNI库


注意,JNI库的名字可以随便取,不过Android平台基本上都采用“lib模块名_jni.so”的命名方式


        Native层对应的是libmedia.so,这个库完成了实际的功能。


        MediaPlayer将通过JNI库libmedia_jni.so和Native层的libmedia.so交互。


        从上面的分析中还可知道:JNI层必须实现为动态库的形式,这样Java虚拟机才能加载它并调用它的函数


        上面代码中列出了两个比较重要的要点:一个是加载JNI库;另一个是Java的native函数


2.1 加载JNI库

        前面说过,如果Java要调用native函数,就必须通过一个位于JNI层的动态库来实现。


顾名思义,动态库就是运行时加载的库,那么在什么时候以及什么地方加载这个库呢?

        这个问题没有标准答案,原则上是:在调用native函数前,任何时候、任何地方加载都可以


通行的做法是在类的static语句中加载,调用System.loadLibrary方法就可以了


这一点在上面的代码中也见到了,我们以后就按这种方法编写代码即可。


另外,System.loadLibrary函数的参数是动态库的名字,即media_jni。


系统会自动根据不同的平台拓展成真实的动态库文件名,例如在Linux系统上会拓展成libmedia_jni.so,而在Windows平台上则会拓展成media_jni.dll。


        从上面代码中可以发现,native_init函数前有Java的关键字native,它表示这个函数将由JNI层来实现


2.2 JNI层MediaPlayer的分析

        上面是JNI层代码,不知道大家看了以后是否会产生一些疑惑?最大的疑惑可能是,如何才能知道Java层的native_init函数对应的是JNI层的android_media_MediaPlayer_native_init(JNIEnv *env)函数呢?

2.3 注册JNI函数

        native_init函数位于android.media这个包中,它的全路径名应该是android.media.MediaPlayer.native_init,而JNI层函数的名字是android_media_MediaPlayer_native_init。


因为在Native语言中,符号“.”有着特殊的意义,所以JNI层需要把Java函数名称(包括包名)中的“.”换成“_”


也就是通过这种方式,native_init找到了自己JNI层的本家兄弟android.media.MediaPlayer.native_init。


        “注册”之意就是将Java层的native函数和JNI层对应的实现函数关联起来,有了这种关联,调用Java层的native函数时,就能顺利转到JNI层对应的函数执行了。


而JNI函数的注册方法实际上有两种,下面分别做介绍。


2.3.1 静态注册

//Todo...

         当Java层调用native_init函数时,它会从对应的JNI库中寻找Java_android_media_MediaPlayer_native_init函数,如果没有,就会报错。


如果找到,则会为这个native_init和android_media_MediaPlayer_native_init建立一个关联关系,其实就是保存JNI层函数的函数指针


以后再调用native_init函数时,直接使用这个函数指针就可以了,当然这项工作是由虚拟机完成的


        静态方法就是根据函数名来建立Java函数和JNI函数之间的关联关系的,而且它要求JNI层函数的名字必须遵循特定的格式


这种方法也有几个弊端,即:

  • 需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah生成一个头文件。


  • javah生成的JNI层函数名特别长,书写起来很不方便


  • 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率


        有什么办法可以克服上面三个弊端吗?根据上面的介绍可知,Java native函数是通过函数指针来和JNI层函数建立关联关系的。


如果直接让native函数知道JNI层对应函数的函数指针,不就万事大吉了吗?这就是下面要介绍的第二种方法:动态注册法。


2.3.2 动态注册

        既然Java native函数和JNI函数是一一对应的,那么是不是会有一个结构来保存这种关联关系呢?答案是肯定的。


在JNI技术中,用来记录这种一一对应关系的,是一个叫JNINativeMethod的结构,其定义如下:

//源码位于:libnativehelper/include_jni/jni.h

typedef struct {
    // Java中native函数的名字,不用携带包的路径,例如“native_init”。


const char* name; // Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合。


const char* signature; // JNI层对应函数的函数指针,注意它是void*类型。


void* fnPtr; } JNINativeMethod;

        应该如何使用这个结构体呢?来看MediaPlayer JNI层是如何做的,代码如下所示:

static const JNINativeMethod gMethods[] = {
    {
        // Java中native函数的函数名
        "native_init",  
        // native_init的签名信息       
        "()V",      
        // JNI层对应的函数指针                        
        (void *)android_media_MediaPlayer_native_init
    },
};


// This function only registers the native methods
static int register_android_media_MediaPlayer(JNIEnv *env)

{
    // 调用AndroidRuntime的registerNativeMethods函数,
    // 第二个参数表明是Java中的哪个类
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}

        AndroidRunTime类提供了一个registerNativeMethods函数来完成注册工作,下面来看registerNativeMethods的实现,代码如下:

/*

 * Register native methods using JNI.

 */

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,

    const char* className, const JNINativeMethod* gMethods, int numMethods)

{
    // 调用jniRegisterNativeMethods函数完成注册

    return jniRegisterNativeMethods(env, className, gMethods, numMethods);

}

        其中jniRegisterNativeMethods是Android平台中为了方便JNI使用而提供的一个帮助函数,其代码如下所示:

        其实动态注册的工作,只用两个函数就能完成。


总结如下:

/*
env指向一个JNIEnv结构体,它非常重要,后面会讨论它。


classname为对应的Java类名, 由于JNINativeMethod中使用的函数名并非全路径名, 所以要指明是哪个类。


*/ jclass clazz=(*env)->FindClass(env,className); //调用JNIEnv的RegisterNatives函数,注册关联关系。


(*env)->RegisterNatives(env,clazz,gMethods,numMethods);

        所以,在自己的JNI层代码中使用这种方法,就可以完成动态注册了。


这里还有一个很棘手的问题:这些动态注册的函数在什么时候和什么地方被调用呢?这里就不卖关子了,直接给出该问题的答案:

        当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数。


如果有,就调用它,而动态注册的工作就是在这里完成的


        所以,如果想使用动态注册方法,就必须实现JNI_OnLoad函数,只有在这个函数中才有机会完成动态注册的工作


静态注册的方法则没有这个要求,但建议大家也实现这个JNI_OnLoad函数,因为有一些初始化工作是可以在这里做的。


        那么,libmedia_jni.so的JNI_OnLoad函数是在哪里实现的呢?由于多媒体系统很多地方都使用了JNI,所以“码农”把它放到android_media_MediaPlayer.cpp中了。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/634442.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-16
下一篇 2022-04-16

发表评论

登录后才能评论

评论列表(0条)

保存