学习JNI--Android下使用JNI调用C

一、什么是JNI:

JNIJava Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

1、使用JNI的好处:

a、可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们,一般是在java中调用C的函数;相反的也可以用C来调用Java中的方法,这样可以复用很多以前写过的代码。

b、JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。

2、使用JNI的副作用:

a、众所周知的Java的可移植性在使用JNI之后可能会被破坏,程序不再跨平台,原因很简单,一个操作系统上的本地方法很可能不能在另一个平台上正常运行,那么导致使用了这些本地方法的Java代码也同样无法在别的平台上运行。

b、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。

一个比较好的做法是,让调用的本地方法只集中在你写的工程的少数几个类中,这样可以降低Java和C代码的耦合性,也提高了代码的可维护性。


二、Java调用C来实现简单的HelloWorld:(以Android+eclipse为例)


好吧,又是HelloWorld!


这里我们以Android工程为例。

在真正开始做之前,还是先来了解一下基本流程:

1、先创建一个Java文件:HelloWorld.java,并且声明一个本地方法,注意要加上一个关键字native,在这里我们不写他的实现,具体实现交给C:

?
1
public native void helloworld();

2、然后我们可以使用javah指令来生成对应java文件的.h头文件(C来使用):

这里我默认已经搭好了环境,如果没有可以参看: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)


3、在工程中新建一个名为jni的文件夹,并将刚才生成的.h文件剪切到jni文件夹中,并写好相应的.c/.cpp代码。

这里有一个小细节就是,有时候eclipse会在#include 这里报一个黄色的问号的警告,找不到这个对应的jni.h文件,我们可以右键点击工程,然后点击AndroidTools->AddNativeSupport,如下图:



点击完之后会弹出一个对话框:



这里我们可以直接写入我们之后想生成的so文件名,这个文件名跟.c文件关联,这里我们选择hello,点击Finish之后,我们发现eclipse自动在jni文件夹中生成了两个新的文件:Android.mkhello.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!" );
     }


4、之后我们需要做的就是生成.so库文件,在系统环境变量配置好了的情况下,直接打开cmd,进入到对应的工程目录下,执行ndk-build即可生成对应的库文件:libhello.so。


实际上这个文件会出现在工程文件夹的:obj/local/armeabi/文件夹下。

至此,函数库就已经生成好了,接下来的工作就是调用这个函数

5、在MainActiviy.java中调用这个.so文件。

我们可以再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();
         }
     }


效果:



你可能感兴趣的:(Android面试)