第一次使用JNI

来源:互联网 发布:淘宝网店如何刷钻 编辑:程序博客网 时间:2024/06/05 00:19

编程语言的第一课一般都是通过 “Hello World”开始的,所以我也从"Hello World"开始学习使用JNI

使用JNI编程需要以下几个工具:

1、android开发环境,包括用到的SDK, Eclispe, JDK,android开发环境的搭建网上有很多,搭建起来就行了。

2、NDK。由google提供,用来编译C/C++源文件。NDK提供了各个平台各个android版本下的头文件和.o文件,使用这些头文件里面定义的函数。具体各个函数的用法可以参考linux应用编程。

NDK环境搭建

在http://developer.android.com/tools/sdk/ndk/index.html点击打开链接 下载相应平台的NDK,我是在windows下开发的,下载后直接双击解压即可。然后将解压后的目录加入到windows环境变量path中,打开windows 控制台,键入命令ndk-build,如果不是说找不到命令,则表示可以使用了。我们可以看看这个ndk-build是个什么文件,在解压后的目录下用记事本打开ndk-build,发现这个文件是一个shell脚本,因此在正常情况下,这个脚本在windows下是不可以使用的,需要安装一些工具。我这里是安装的MinGW,也是在window下使用的比较多的,怎么安装,baidu即可。

使用JNI编程。

step1:编程Java程序

打开Eclipse,创建android工程,我这里创建的工程为NDKTest。

package com.example.ndktest;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends Activity {static{System.loadLibrary("hello_world"); //加载静态库}    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);                TextView text = (TextView)findViewById(R.id.textView1);                text.setText(getText()); //调用native方法。    }    private native String getText(); //native方法,返回的值为文本框的text.}

这里创建了一个简单的文本框,并调用了native方法,显示native方法返回的文本。所以在java中使用native方法很简单,只需要将函数名前加一个native关键字即可。并且在使用native函数之前,加载包含该函数的动态库即可。

到此,我们的java程序算是写完了,接下来编译android程序,得到.class文件。为什么需要.class文件?接下来在step2就知道了。实际上在这里我们只用编译MainActivity.java,因为只有这个类中用到了native函数,得到MainActivity.class。该文件放在工程目录下的bin/classes/{package}目录下。

step2:生成头文件

第一步我们在java程序中申明并调用了native函数,但是我们的native函数具体定义应该是什么样的呢?这两个函数的调用关系是怎样建立起来的?我们先执行完以下的步骤再回过来看这两个问题。

使用javah 工具生成相应的头文件。


javah生成我们需要的头文件,其中-d表示我们要生成的头文件的目录,-classpath表示我们的类的路径,最后的"com.example.ndktest.MainActivity"是我们在step1中生成的MainActivity.class的路径。这里有三个地方要注意:

1、一般-d 接的目录为jni,原因会在step4中讲述。

2、-classpath 接的参数为class路径,如果没有android.jar目录,则会出现找不到Activity类的错误,因为我们的MainActivity是继承的Activity类。

3、最后的参数com.example.ndktest.MainAcitivy是我们类的路径,而不是绝对路径名。

OK,到这里如果顺利的话就会生成我们的头文件了,因为我们是在 C:\Users\Administrator 目录下运行的javah命令,所以到该目录下就可以找到jni目录并且生成了相应的头文件,我们来看看头文件是什么样子的。


com_example_ndktest_MainActivity.h:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h> /* Header for class com_example_ndktest_MainActivity */#ifndef _Included_com_example_ndktest_MainActivity#define _Included_com_example_ndktest_MainActivity#ifdef __cplusplusextern "C" {#endif#undef com_example_ndktest_MainActivity_MODE_PRIVATE#define com_example_ndktest_MainActivity_MODE_PRIVATE 0L#undef com_example_ndktest_MainActivity_MODE_WORLD_READABLE#define com_example_ndktest_MainActivity_MODE_WORLD_READABLE 1L#undef com_example_ndktest_MainActivity_MODE_WORLD_WRITEABLE#define com_example_ndktest_MainActivity_MODE_WORLD_WRITEABLE 2L#undef com_example_ndktest_MainActivity_MODE_APPEND#define com_example_ndktest_MainActivity_MODE_APPEND 32768L#undef com_example_ndktest_MainActivity_MODE_MULTI_PROCESS#define com_example_ndktest_MainActivity_MODE_MULTI_PROCESS 4L#undef com_example_ndktest_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING#define com_example_ndktest_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING 8L#undef com_example_ndktest_MainActivity_BIND_AUTO_CREATE#define com_example_ndktest_MainActivity_BIND_AUTO_CREATE 1L#undef com_example_ndktest_MainActivity_BIND_DEBUG_UNBIND#define com_example_ndktest_MainActivity_BIND_DEBUG_UNBIND 2L#undef com_example_ndktest_MainActivity_BIND_NOT_FOREGROUND#define com_example_ndktest_MainActivity_BIND_NOT_FOREGROUND 4L#undef com_example_ndktest_MainActivity_BIND_ABOVE_CLIENT#define com_example_ndktest_MainActivity_BIND_ABOVE_CLIENT 8L#undef com_example_ndktest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT#define com_example_ndktest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L#undef com_example_ndktest_MainActivity_BIND_WAIVE_PRIORITY#define com_example_ndktest_MainActivity_BIND_WAIVE_PRIORITY 32L#undef com_example_ndktest_MainActivity_BIND_IMPORTANT#define com_example_ndktest_MainActivity_BIND_IMPORTANT 64L#undef com_example_ndktest_MainActivity_BIND_ADJUST_WITH_ACTIVITY#define com_example_ndktest_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L#undef com_example_ndktest_MainActivity_CONTEXT_INCLUDE_CODE#define com_example_ndktest_MainActivity_CONTEXT_INCLUDE_CODE 1L#undef com_example_ndktest_MainActivity_CONTEXT_IGNORE_SECURITY#define com_example_ndktest_MainActivity_CONTEXT_IGNORE_SECURITY 2L#undef com_example_ndktest_MainActivity_CONTEXT_RESTRICTED#define com_example_ndktest_MainActivity_CONTEXT_RESTRICTED 4L#undef com_example_ndktest_MainActivity_RESULT_CANCELED#define com_example_ndktest_MainActivity_RESULT_CANCELED 0L#undef com_example_ndktest_MainActivity_RESULT_OK#define com_example_ndktest_MainActivity_RESULT_OK -1L#undef com_example_ndktest_MainActivity_RESULT_FIRST_USER#define com_example_ndktest_MainActivity_RESULT_FIRST_USER 1L#undef com_example_ndktest_MainActivity_DEFAULT_KEYS_DISABLE#define com_example_ndktest_MainActivity_DEFAULT_KEYS_DISABLE 0L#undef com_example_ndktest_MainActivity_DEFAULT_KEYS_DIALER#define com_example_ndktest_MainActivity_DEFAULT_KEYS_DIALER 1L#undef com_example_ndktest_MainActivity_DEFAULT_KEYS_SHORTCUT#define com_example_ndktest_MainActivity_DEFAULT_KEYS_SHORTCUT 2L#undef com_example_ndktest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL#define com_example_ndktest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L#undef com_example_ndktest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL#define com_example_ndktest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L/* * Class:     com_example_ndktest_MainActivity * Method:    getText * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_ndktest_MainActivity_getText  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
头文件的名字我们可以通过 -o 来指定,如果不指定则默认为 {packagename.classname}.h。现在生成了头文件,头文件中申明了jni函数(看最后几行),函数的命名很长,java函数中调用的native函数就是调用的这个函数,那么接下来就是函数的实现了。

step3:jni函数的实现

新建一个.c文件,名字可以随便取,为了和前面生成的头文件对应,我新建了一个com_example_ndktest_MainActivity.c的文件,并且将其放到和.h同一个目录下,内容如下:

#include <string.h>#include <jni.h>jstring Java_com_example_ndktest_MainActivity_getText(JNIEnv *env, jobject thiz){return (*env)->NewStringUTF(env, "Hello World");}


在这个c文件中,我实现了在上面那个头文件中声明的方法。很简单,就是返回一个字符串,jni的语法可以再深入研究一下。接下来就是编译生成.so文件了。

step4:生成.so文件

我们的C文件也写完了, 接下来就是怎样生成.so文件,也就是我们的静态库文件,在java程序里面的 System.LoadLibrary("hello_world"); 就是加载在此处生成的.so文件。

在生成.so文件之前还需要新一个makefile文件,如下所示,文件名为Android.mk,也放在jni文件夹下。

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := hello_worldLOCAL_SRC_FILES := com_example_ndktest_MainActivity.cinclude $(BUILD_SHARED_LIBRARY)
其实LOCAL_MODULE为我们希望生成的静态库的名称,LOCAL_SRC_FILES是要编译到的源文件,多个文件以空格隔开。
然后按win+R输入 cmd打开windows的控制终端,并进入到 jni 目录或其上一级目录,运行ndk-build命令,会看到如下的执行结果。

我们可以看到生成了名称为libhello_world.so的文件,实际程序加载时也是加载的这个静态库,只是我们书写时只需要写hello_world就可以了,剩余的工作系统会自动帮我们完成。

我们进入到C:\Users\Xxing 目录,发现除了之前在生成.h文件时生成的jni文件夹之外,编译.so文件之后还生成了另外两个文件夹,将这三个文件夹拷贝到android项目文件夹下。(实际上我们可以将我们控制台的工作目录切换到android的工作目录,这样就不用拷贝文件夹这么麻烦)。

我们在step2的时候说过,一般在生成.h头文件的时候指定文件夹名称为 jni,这是因为如果不为jni,运行ndk-build的时候则会出现"Please define the NDK_PROJECT_PATH variable to point to it" 的错误,这里我们只需要将编译的文件放入到jni文件夹即可。

或者也可以采用以下的方法,运行ndk-build并指定以下变量:


step5:重新编译android程序。

回到eclipse,重新编译android程序,并运行,则可出现如下结果:







0 0