一、什么是JNI
在Android Framework 中,需要提供一种媒介或桥梁,将Java 层(上层)与C\C++层(底层)有机联系起来,使得他们相互协调,共同完成某些任务,充当该桥梁的就是Java本地接口JNI(Java Native Interface)。它允许Java代码与基于C/C++编写的应用程序和库进行交互操作。
二、什么情况下要用到JNI
1)注重处理速度
与本地代码C/C++相比,Java的代码执行速度要慢一些,如果对程序的执行速度有较高的要求,建议用C\C++编写代码,然后在Java中通过JNI调用基于C/C++编写的部分
2)硬件控制
为了更好地控制硬件,硬件代码通常用C语言编写,然后借助JNI将其与Java层连接起来,从而实现对硬件 的控制
3)既有C/C++代码的复用
在程序编写的过程中,常常有一些已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性和健壮性。在复用这些C/C++代码时就要通过JNI来实现
三、JNI的基本原理
1、在Java中调用C库函数
在Java代码中通过JNI调用C函数的步骤如下:
第一步:编写Java代码
第二步:编译Java代码
第三步:生成C语言头文件
第四步:编写C代码
第五步:生成C共享库
第六步:运行Java程序
1)编写Java代码
要想在Java代码中通过JNI 调用C函数,首先在Java类中声明本地方法,本地方法仅在Java程序中进行声明,使用C/C++等本地语言来实现
<span style="font-size:14px;">class HelloJNI
{
//本地方法声明
native void printHello();
native void printString(String str);
//加载库
static{System.loadLibrary("hellojni");}
public static void main(String args[])
{
HelloJNI myJNI = new HelloJNI();
//调用本地方法,即调用使用语言编写的JNI本地函数
myJNI.printHello();
myJNI.printString("Hello World from printSteing fun");
}
}</span>
在Java中,使用native 关键字声明本地方法,该方法与用C/C++编写的JNI本地函数相对应。native关键字告知Java编译器,在Java代码中带有该关键字的方法只是申明,具体有C/C++等其他语言编写实现。如果去掉native关键字,代码编译时则会报错。
在上述代码中的Java类仅声明了两个本地方法,接着再调用System.loadLibrary() 方法,加载具体实现本地方法的C运行库
2)编译Java代码
上述代码用JDK编译生成HelloJNI.class 文件
3)生成C语言头文件
Java代码编写、编译完成后,将创建 hellojni.dll 运行库文件,具体实现HelloJNI类中声明的两个本地方法printHello()与printString()
在HellloJNI 类中声明了printHello() 本地防范后,就要在hellojni.dll 运行库总实现一个相同签名的printHello()函数
但是当在Java类中调用使用native 关键字声明的printHello()方法时,Java虚拟机并不是调用hellojni.dll 运行库中的printHello()函数,而是调用函数原型,如果直接编写好C代码然后编译生成hellojni.dll 文件,则程序运行时会产生 java.langUnsatistisfiedLinkError 错误
要想创建本地方法的映射C函数,就必须先生成函数原型,函数原型位于C/C++头文件中。只有生成了函数原型,Java 虚拟机才能把本地库相应的函数与Java 本地方法映射在一起。
Java 提供了javah 工具,它位于<JDK_HOME>\bin 目录下,用来生成包含函数原型的 C/C++ 头文件,其使用方法如下:
javah <包含以native 关键字声明的方法的Java类名称> 如
javah HelloJNI
//生成头文件
HelloJNI.class
HelloJNI.h
HelloJNI.java
HelloJNI.h 中的函数
内容如下
JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *,jobject);
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *,jobject,jstring);
上述两个函数原型对应于Java代码中的本地方法,函数名称遵循 “
Java_类名_本地方法名” 的命名规则,只要生成了这样的函数原型,Java虚拟机就能把本地库函数与Java本地方法正常连接在一起,而JNIEXPOER、JNICALL 都是JNI关键字,表示此函数要被 JNI 调用,函数原型中必须有这两个关键字。
函数原型中,带有两个默认参数,分别为JNIEnv * ,jobject,支持JNI的函数必须包含这两个共同参数,第一个JNIEnv * 是JNI接口的指针,用来调用JNI表中的各种JNI参数,第二个默认参数jobject也是JNI提供的Java本地类型,用来在C代码中访问Java对象;第二个函数原型第三个函数由函数native 方法中的参数决定,如:
printString(String str)——> void Java——HelloJNI_printString(JNIEnv *,jobject,jstring)
HelloJNI.java (声明) <——> HelloJNI.h ( 具体实现 )
4)编写C/C++代码
C函数原型生成之后,开始编写hellojni.c文件,具体实现JNI本地函数
5)生成C共享库
window环境下,用VS软件可编译C文件,生成 .dll 文件
6)运行Java 文件