JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
a、可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们,一般是在java中调用C的函数;相反的也可以用C来调用Java中的方法,这样可以复用很多以前写过的代码。
b、JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。
a、众所周知的Java的可移植性在使用JNI之后可能会被破坏,程序不再跨平台,原因很简单,一个操作系统上的本地方法很可能不能在另一个平台上正常运行,那么导致使用了这些本地方法的Java代码也同样无法在别的平台上运行。
b、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。
一个比较好的做法是,让调用的本地方法只集中在你写的工程的少数几个类中,这样可以降低Java和C代码的耦合性,也提高了代码的可维护性。
好吧,又是HelloWorld!
这里我们以Android工程为例。
在真正开始做之前,还是先来了解一下基本流程:
1
|
public native void helloworld();
|
这里我默认已经搭好了环境,如果没有可以参看:http://www.cnblogs.com/baronzhao/archive/2012/07/10/2585181.html
打开cygwin,我们首先进入到对应工程的src目录下,在我这里是使用的cygwin打开的目 录:/cygdrive/d/Android_Workspace/JNI_day19/Exercise/src
然后执行:javah -jni com.example.exercise.MainActivity,其中com.example.exercise.MainActivity为MainActivity.java的全类名
正常的话就会在src目录下生成一个.h的头文件:com_example_exercise_MainActivity.h,其内容为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_example_exercise_MainActivity */
#ifndef _Included_com_example_exercise_MainActivity
#define _Included_com_example_exercise_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_exercise_MainActivity
* Method: getStr
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_exercise_MainActivity_getStr(
JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
|
实际上上面那些endif之类的宏定义都不是必须的,关键的部分只有一个include和JNIEXPORT jstring JNICALL Java_com_example_exercise_MainActivity_getStr(JNIEnv *, jobject);
其中,jni.h是jni定义的对应java中的类型和方法的c中的对应类型和函数的头文件,这个是必须有的。
其次,JNIEXPORT jstring JNICALL Java_com_example_exercise_MainActivity_getStr(JNIEnv *, jobject); 这一句除了JNIEXPORT可以去掉之外,其他的必须一致保留,大概写法就是:返回类型(这里是void) java_全类名(.换成_)_方法名(JNIEnv* env,jobject ojb);
这里有一个小细节就是,有时候eclipse会在#include 这里报一个黄色的问号的警告,找不到这个对应的jni.h文件,我们可以右键点击工程,然后点击AndroidTools->AddNativeSupport,如下图:
点击完之后会弹出一个对话框:
这里我们可以直接写入我们之后想生成的so文件名,这个文件名跟.c文件关联,这里我们选择hello,点击Finish之后,我们发现eclipse自动在jni文件夹中生成了两个新的文件:Android.mk和hello.cpp<喎�"/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPs7EvP48L3A+CjxwPsbk1tBBbmRyb2lkLm1rzsS8/s6qo7o8L3A+CjxwPiA8L3A+CjxwcmUgY2xhc3M9"brush:java;">LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #对应的是打包成函数库的名字 LOCAL_MODULE := hello #src目录,对应的是c代码的文件 LOCAL_SRC_FILES := hello.cpp include $(BUILD_SHARED_LIBRARY)
这个文件的作用是指定生成.so文件的规则。
而hello.cpp文件则是我们需要实际写c代码的文件,在这里面,我们将实现刚才头文件中的jstring JNICALL Java_com_example_exercise_MainActivity_getStr(JNIEnv *, jobject);这个函数。
实际上这里要做的也很简单,我们的目的就是想在这个函数里返回一个字符串:"hello from C!",实际上这里就需要用到jni.h中定义的一个方法了:jstring (*NewStringUTF)(JNIEnv*, const char*);它的作用就是返回一个jstring字符串
1
2
3
4
5
6
7
|
#include
#include "com_example_err_MainActivity.h"
//jstring (*NewStringUTF)(JNIEnv*, const char*);
JNIEXPORT jstring JNICALL Java_com_example_err_MainActivity_helloWorld(JNIEnv * env, jobject obj) {
return (*env).NewStringUTF( "hello from c!" );
}
|
实际上这个文件会出现在工程文件夹的:obj/local/armeabi/文件夹下。
至此,函数库就已经生成好了,接下来的工作就是调用这个函数
我们可以再MainActiviy.java文件下,定义一个Button,对应点击事件cilck,每当点击的时候,就调用这个方法,将其返回的字符串"hello from c!"以Toast的形式打印出来,在这之前需要注意的是,我们需要先加载刚才生成的.so库文件,这里使用一个static块来加载,System.loadLibrary("hello");,注意这里我们不需要写libhello.so,而只需要写hello即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class MainActivity extends Activity {
public native String helloWorld();
static {
System.loadLibrary( "hello" );
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
String helloWorld = helloWorld();
System.out.println(helloWorld);
Toast.makeText(getApplicationContext(), helloWorld, Toast.LENGTH_LONG)
.show();
}
}
|
效果: