作者:EasyWave 时间:2015.01.25
类别:Android系统-基于Android2.3.5系统:JNI与HAL实例解析[一] 声明:转载,请保留链接
注意:如有错误,欢迎指正。这些是我学习的日志文章......
***************************************************************************************************************************
一:Android系统下JNI简介
Android系统下的JNI的全称是:Java Native Interface (JNI),JNI标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行交互操作。
二:为什么Java要推出JNI
JNI在Android系统中有着广泛的应用。Android系统底层都是C/C++实现的,上层提供的API都是Java的,Java通过JNI调用底层的实现。
- Java世界虚拟机使用Native语言编写的,虚拟机运行在具体的平台上,虚拟机本身无法做到与平台无关,jni可以对java层屏蔽不同平台操作的差异,这样就能实现java本身平台无关特性。
- 适应已经用Native语言实现的技术。
- 一些效率的问题需要Native语言实现。
在Java中使用JNI主要是为了以上几点而实现的,在Anroid中的API多媒体接口MediaPlayer类,其实底层通过JNI调用libmedia库。由于JNI的存在可以让我们重用很多已经存在C/C++的库,省去了重复开发的麻烦,并且可以利用很多开源的库(Android库中就有很多开源库,比如libjpeg,libpng等等),并且让我们开发的程序更有效率(C/C++代码发挥硬件最佳性能)。
三:Java中JNI调用过程
在Java的世界里,JNI对于应用本身来说,可以看做一个代理模式,而对于开发者来说,需要使用C/C++来实现一个代理程序来实际操作目标原生函数,Java程序中则是JVM通过加载并调用JNI程序间接地调用目标原生函数。其调用的示意图如下:
图一:Android下JNI调用过程(Load DLL,DLL根据不同的环境而定)
Android系统Java下JNI程序开发的一般操作步骤如下:
- 编写JAVA中的调用类;
- 用javah生成C/C++原生函数的头文件;
- C/C++中调用需要的其他函数功能,实现原生函数;
- 将项目依赖的所有原生库和资源加到java项目的路径中;
- 生成JAVA程序;
- 发布JAVA应用和so库;
四:Android系统下JNI语法
Android系统Java下的JNI对于应用本身来说,可以看做一个代理模式,对于开发者来说,需要使用C/C++用一个代理程序来实际操作目标原生函数,其一,基本的数据类型转换,如下所示:
图二:基本数据类型转换表
备注:符号属性是指在JNI环境下
其二,引用数据类型转换表,如下所示:
图三:引用数据类型转换表
五:Java下JNI注册函数
Java下的JNI命名规则:JNI层需要将JAVA函数名称(包括包名)中的“.”换成“_”,用这种方式可以找到自己JNI层的路径以及文件。注册Native函数有两种方法:静态注册和动态注册。静态注册静态就是根据函数名来建立java和jni函数之间的关联,而且要求jni层函数的名字必须遵循特定的格式,其缺点在于:需要编译所有的java声明的类;javah生成的jni层函数特别长;初次调用native函数时要根据名字搜索对应的jni层函数来建立关联联系,这样影响效率。因此这里只详细的讲解动态注册的方式,Java native与JNI通过JNINativeMethod的结构来建立联系,其结构内容如下:
typedef struce {用一个Anroid系统下的实例来感性的认识和说明一下,(请到网上去下载Anroid2.3.4的OK6410的Anroid代码,跟本文的路径一致,因为我自己把这个Android2.3.4的OK6410移植到Android2.3.5的平台上)路径为android2.3.5/frameworks\base\services\forlinx_pwm_jni\PwmJniService.cpp,这是一个PWM控制的JNI,具体的如下所示: 图四:JNINativeMethod方法 Android提供一个RegisterNatives(JNIEnv *env, Const char* className, Const JNINativeMethod* gMethods, int numMethods)来实现这个功能,其实还有一个在AndroidRuntime.cpp中也实现了一个RegisterNatives函数,两者的功能是相同的,如下所示: 我们来看看PwmJniService.cpp函数中的JNI动态注册吧,如下所示:
const char* name; //java中native函数的名字,不用携带包的路径
const char*signature; //java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合
void *fnptr; //JNI层对应的函数指针,注意他是void*的类型。
} JNINativeMethod;
在JNI_OnLoad函数中,会去调用register_forlinx_server_PwmService()这个函数,而这个函数的定义如下所示:
一起来看看这行代码:static const char* const kClassName ="forlinx_pwm_server/server/PwmService";,就是通过kClassName找到相应的库的,而这个PwmService就是在Android2.3.5/frameworks\base\services\forlinx_pwm_server\server\PwmService.java文件,如下所示:
也就是说Java层通过这种手段实现了Java层去调用C/C++层的库,这样就将Anroid底层给封装起来了,而在Android的JNI层会去调用Android的HAL层,而HAL层调用的就是Linux的驱动!!
六:JNI的垃圾回收
Java中创建的对象最后由垃圾回收器来回收和释放内存的,而对于JNI,直接对对象进行引用后不会增加引用计数值,当从JNI返回后在JNI引用的对象有可能被垃圾回收器回收,而在JNI下面记录的指针有可能是野指针,为了解决此类问题,JNI规范提供如下三类引用:
- Local Refrence:本地引用,在JNI层函数中使用的非全局变量都是采用Local Refrence,它包含函数调用时传入的jobject和在JNI层函数中创建的jobject,其最大的特点是一旦JNI层函数返回,这些jobject就可能被垃圾回收了。
- Global Refrence:全局引用,这类引用如果程序不主动调用销毁接口将一直驻留在内存中。
- Weak Global Refrence:弱全局引用,在使用过程中有可能被释放,调用JNIEnv中IsSameObject函数来判断是否仍然存在。
如果java函数的参数是class,则以“L”开头,以“;”结尾,中间是用“/”隔开的包及类名,对应的C函数名的参数则为jobject,一个例外是String类,其对应类为jstring。如下所示:
Ljava/lang/String; String jstring以android2.3.5/frameworks\base\services\forlinx_pwm_jni\PwmJniService.cpp中的static const JNINativeMethod gMethods[]数组函数为例子,如下所示:
Ljava/net/Socket; Socket jobject
选取forlinx_set_freq函数来说明JNI签名的问题,在上图中可以看到 { "_set_freq", "(I)Z", (void *)forlinx_set_freq }, 这样的定义方式,对照上表中的签名解析表格,我们就一目了然啦。forlinx_set_freq函数的定义如下所示:
可以看出来forlinx_set_freq的返回值类型为:jboolean,而jboolean所代表的JNI签名为:Z,而jint freq是forlinx_set_freq函数中需要设置的一个值,而jint所代表的JNI的签名为:I 因此forlinx_set_freq函数的签名信息为:(I)Z。