编程语言的第一课一般都是通过 “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 __cplusplus
extern "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");
}
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_world
LOCAL_SRC_FILES := com_example_ndktest_MainActivity.c
include $(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程序,并运行,则可出现如下结果: