第一次使用JNI

编程语言的第一课一般都是通过 “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");
}


在这个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_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程序,并运行,则可出现如下结果:







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值