首页 > 代码库 > 学习JNI--Android下使用JNI调用C

学习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:

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,其内容为:

/* DO NOT EDIT THIS FILE - it is machine generated */
	#include <jni.h>
	/* 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<jni.h>和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>这里报一个黄色的问号的警告,找不到这个对应的jni.h文件,我们可以右键点击工程,然后点击AndroidTools->AddNativeSupport,如下图:



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



这里我们可以直接写入我们之后想生成的so文件名,这个文件名跟.c文件关联,这里我们选择hello,点击Finish之后,我们发现eclipse自动在jni文件夹中生成了两个新的文件:Android.mkhello.cpp文件

其中Android.mk文件为:

 

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字符串

#include <stdio.h>
	#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即可

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();
		}
	}


效果:












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