android中JNI的用途及简单使用

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

JNI的角色

编辑
JNI可以这样与本地程序进行交互:
1、你可以使用JNI来实现“本地方法”( native methods),并在JAVA程序中调用它们。
2、JNI支持一个“调用接口”(invocation interface),它允许你把一个 JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。例如,一个用C语言写的浏览器可以在一个嵌入式JVM上面执行从网上下载下来的applets。

JNI的副作用

编辑
一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:
1、程序不再 跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个 类当中。这样就降低了JAVA和C之间的 耦合性。

什么场合下应该使用JNI

编辑
当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:
1、JAVA程序和本地程序使用 TCP/IP或者 IPC进行交互。
2、当用JAVA程序连接本地数据库时,使用 JDBC提供的API。
3、JAVA程序可以使用分布式对象技术,如JAVA  IDL API。
这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。
下面这些场合中,同一进程内JNI的使用无法避免:
1、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。
2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。
3、JAVA程序当中的一部分代码对效率要求非常高,如算法计算,图形渲染等。
总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。

JNI的演化

编辑
JDK1.0包含了一个本地方法接口,它允许JAVA程序调用C/C++写的程序,许多第三方的程序和JAVA类库。如:java.lang,java.io,java.net等都依赖于本地方法来访问底层 系统环境的特征。
不幸的是,JDK1.0中的本地方法有两个主要问题:
1、本地方法像访问C中的结构(structures)一样访问对象中的 字段。尽管如此,JVM规范并没有定义对象怎么样在内存中实现。如果一个给定的JVM实现在布局对象时,和本地方法假设的不一样,那你就不得不重新编写本地方法库。
2、因为本地方法可以保持对JVM中对象的直接指针,所以,JDK1.0中的本地方法采用了一种保守的 GC策略。
JNI的诞生就是为了解决这两个问题,它可以被所有平台下的JVM支持:
(1)每一个JVM实现方案可以支持大量的本地代码。
(2)开发工具作者不必处理不同的本地方法接口。
(3)本地代码可以运行在不同的JVM上面。
JDK1.1中第一次支持JNI,但是,JDK1.1仍在使用老风格的本地代码来实现JAVA的API。这种情况在JDK1.2下被彻底改变成符合标准的写法。

JNI的设计目的

编辑
标准的java类库可能不支持你的程序所需的特性。或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。
你可能需要用底层语言实现一个小型的时间敏感代码,比如 汇编,然后在你的java程序中调用这些功能。

书写步骤

编辑
·编写带有native声明的方法的java类
·使用 javac命令编译所编写的java类
android中JNI的用途及简单使用_第1张图片
JNI (2张)
,然后使用javah + java类名生成扩展名为h的头文件
·使用C/C++实现本地方法
·将C/C++编写的文件生成 动态连接库
·ok
1) 编写java程序:这里以HelloWorld为例。
代码1:
public class HelloWorld {
    public native void displayHelloWorld();//所有native关键词修饰的都是对本地的声明
    static {
        System.loadLibrary("hello");//载入本地库
    }
    public static void main(String[] args) {
        new HelloWorld().displayHelloWorld();
    }
}
声明 native方法:如果你想将一个方法做为一个本地 方法的话,那么你就必须声明该方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。 Load动态库:System.loadLibrary("hello");加载 动态库(我们可以这样理解:我们的方法 displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以 static块进行加载的。同时需要注意的是System. loadLibrary();的参数“hello”是动态库的名字。
2) 编译
没有什么好说的了 javac HelloWorld.java
3) 生成扩展名为h的头文件javah HelloWorld
jni HelloWorld 头文件的内容:
/*DO NOT EDI TTHIS FILE - it is mach inegenerated*/
#include
/*Header for class HelloWorld*/

#ifndef_Included_HelloWorld
#define_Included_HelloWorld
#ifdef__cplusplus
extern"C"{
#endif
/*
*Class:HelloWorld
*Method:displayHelloWorld
*Signature:()V
*/
JNIEXPORTvoidJNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv*,jobject);

#ifdef__cplusplus
}
#endif
#endif
android中JNI的用途及简单使用_第2张图片 JNI
(这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写 C/C++程序的时候所使用的方法名必须和这里的一致)。
4) 编写本地方法实现和由javah命令生成的头文件里面声明的方法名相同的方法。
代码2:
#include"jni.h"
#include"HelloWorld.h"

//#includeotherheaders

JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv*env,jobject obj)
{
printf("Helloworld!\n");
return;
}
android中JNI的用途及简单使用_第3张图片 JNI
注意代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnv、 jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(我是这么理解的:相当于我们在编写java程序的时候,实现一个 接口的话需要声明才可以,这里就是将HelloWorld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为 HelloWorldImpl.c就ok了。
5) 生成动态库
这里以在Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用 VC的 编译器cl成。 cl -I%java_home%\include -I%java_home%\include\win32 -LD HelloWorldImp.c -Fehello.dll 注意:生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include -I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
如果配置了MinGW,也可以这样来编译:gcc -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -Id:/java/include –Id:/java/include/win32 -shared -o (输出的dll文件名,如sum.dll) (输入的c/c++源文件,如abc.c)。
6) 运行程序
javaHelloWorld就ok.
如果用eclipse,需将dll或so文件放在项目下,而不是src及其子目录下。
如果用命令行编译,把dll文件放在该包的同目录下。

使用例子

编辑

今天开始研究JNI技术,首先还是老套路,输出一个HelloWorld:具体流程如下:在Java中定义一个方法,在C++中实现这个方法,在方法内部输出“Hello World",然后再回到Java中进行调用。分为以下步骤:

第一步:在Eclipse中建立一个类:JNIDemo

[java]  view plain  copy
  1. package com.jni.demo;  
  2. public class JNIDemo {  
  3.     //定义一个本地方法  
  4.     public native void sayHello();  
  5.     public static void main(String[] args){  
  6.         //调用动态链接库  
  7.         System.loadLibrary("JNIDemo");  
  8.         JNIDemo jniDemo = new JNIDemo();  
  9.         jniDemo.sayHello();  
  10.     }  
  11. }  
其中sayHello就是要在C++中实现的方法。


第二步:使用javah命令将JNIDemo生成.h的头文件:

命令如下:

E:\workspace\JNIDemo\bin>javah com.jni.demo.JNIDemo

注意:

1. 首先要确保配置了Java的环境变量的配置,不然javah命令不能用,具体怎么配置见:http://blog.csdn.net/jiangwei0910410003/article/details/17463173

2. 我的Java项目是放在E:\workspace中的,所以首先进入到工程的bin目录中,然后使用javah命令生成头文件

3. javah后面的类文件的格式:是类的全名(包名+class文件名),同时不能有.class后缀

命令执行成功后会在bin目录中生成头文件:com_jni_demo_JNIDemo.h

android中JNI的用途及简单使用_第4张图片

但是我们还需要注意一个问题,就是如果我们的包含native方法的类,如果引用其他地方的类,那么这时候进入bin\classes\目录下会出现问题提示找不到指定的类,这时候我们需要切换到源码目录src下运行即可。


第三步:使用VC6.0生成.dll文件:

首先创建一个dll工程:

android中JNI的用途及简单使用_第5张图片

android中JNI的用途及简单使用_第6张图片

android中JNI的用途及简单使用_第7张图片

在.cpp文件中输入如下代码:

[cpp]  view plain  copy
 
  1. "font-size:14px;">#include  
  2. #include "com_jni_demo_JNIDemo.h"  
  3.   
  4. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)  
  5. {  
  6. cout<<"Hello World"<
  7. }  

说明:

1. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
{
cout<<"Hello World"< }

这个方法的声明可以在上面生成的com_jni_demo_JNIDemo.h头文件中找到,这个就是Java工程中的sayHello方法的实现

2. 这里编译会出现几个问题:

(1):会提示你找不到相应的头文件:

android中JNI的用途及简单使用_第8张图片

这时候需要将jni.h,jni_md.h文件考到工程目录中,这两个文件的具体位置在:

android中JNI的用途及简单使用_第9张图片

java的安装目录中的include文件夹下,jni_md.h这个文件在win32文件夹中,找到这两个文件后,将其拷贝到C++的工程目录中;

(2) 当拷贝到这两个文件之后,编译还是提示找不到这两个文件:主要原因就是#include这个是从系统目录中查找jni.h头文件的,而我们只把jni.h拷贝到工程目录中,所以需要在com_jni_demo_JNIDemo.h头文件中将#include改成#include "jni.h",同理在jni.h文件中将#include改成#include "jni_md.h"

(3) 同时还有一个错误就是,提示:e:\c++\jnidemo\jnidemo.cpp(9) : fatal error C1010: unexpected end of file while looking for precompiled header directive,这个是预编译头文件读写错误,这时候还要在VC中进行设置:项目-》设置-》C/C++;在分类中选择预编译头文件,选择不使用预补偿页眉:

android中JNI的用途及简单使用_第10张图片

这样,编译成功,生成JNIDemo.dll文件在C++工程中的Debug目录中


第四步:将JNIDemo.dll文件添加到path环境变量中:

android中JNI的用途及简单使用_第11张图片

注意:在用户变量中的path设置,用分号隔开: ” ;E:\C++\Debug“,这样就将.dll文件添加到环境变量中了


第五步:在Eclipse中调用sayHello方法输出"Hello World":代码如下:

[java]  view plain  copy
  1. public static void main(String[] args){  
  2.     //调用动态链接库  
  3.     System.loadLibrary("JNIDemo");  
  4.     JNIDemo jniDemo = new JNIDemo();  
  5.     jniDemo.sayHello();  
  6. }  

System.loadLibrary方法就是加载JNIDemo.dll文件的,一定要注意不要有.dll后缀名,只需要文件名即可;

注意:运行的时候会报错:

android中JNI的用途及简单使用_第12张图片

这个提示就是没有找到JNIDemo.dll文件,这时候我们需要关闭Eclipse,然后在打开,运行就没有错了,原因是Eclipse每次打开的时候都会去读取环境变量的配置,我们刚才配置的path,没有立即生效,所以要关闭Eclipse,然后从新打开一次即可。


你可能感兴趣的:(android中JNI的用途及简单使用)