这里先讲几个符号的约定:
<project> - 你的Android应用程序工程的目录
<ndk> - 你的ndk安装的目录
捷径这里先扯一句题外话 -- 如果你不需要使用NDK开发,只是需要使用第三方用NDK开发的库,那么你只需要这样做:
把第三方提供的libxxx.so放到你的<project>/libs/armeabi/下, 然后在程序随便什么地方中加入
static {
System.loadLibray("xxx");
}
就可以使用该库了。
安装NDKNDK的安装很简单:
1. 首先要将SDK升级至最新,然后下载ndk(可能要翻墙,恩恩)将它解压到某个目录<ndk>下。
2. 运行: <ndk>/build/host-setup.sh(注:r4版本或更高版本,无须执行这步,详情请看ndk/doc/install.html)
3. 如果成功的话就OK了,如果失败的话检查一下你是不是下载了正确的ndk版本(例如你的操作系统是linux而下载了windows版的ndk).
Java部分现在我们用一个很简单的例子来说明NDK的使用。我们在eclipse中新建一个android工程,其中:
Project Name:jnitest
Build Target: Android 2.2
Application Name: JNI Test
Package Name: org.eshock.jnitest
Create Activity: JNITest
JNITest.java:
package org.eshock.jnitest;
import android.app.Activity;
import android.os.Bundle;
public class JNITest extends Activity {
public native int plus (int x, int y);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int x = plus(1, 2);
android.util.Log.d("jni", String.valueOf(x));
}
static {
System.loadLibrary("mylib");
}
}
我们只是演示NDK,所以就不要界面了。在这个程序中,我们调用一个c语言编写的plus(int, int)函数来计算1+2的值,然后在log中打印它。这个库的名称叫做mylib。
要使用一个c语言的函数,需要在java中声明:
public native int plus(int x, int y);
这样java编译器就知道这个函数是外部库中实现的。
这里需要提到的一点就是在JNI里面,有两个很重要的函数
- jint JNI_OnLoad(JavaVM* vm, void* reserved)
- {
- void *venv;
- LOGI("JNI_OnLoad!");
- if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed");
- return -1;
- }
- return JNI_VERSION_1_4;
- }
- jint JNI_OnUnload(JavaVM* vm,void* reserved)
- {
- void *venv;
- LOGI("JNI_OnUnload!");
- if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed");
- return -1;
- }
- return JNI_VERSION_1_4;
- }
上面的函数是指如果我们需要在加载SO库或者释放SO库的时候,需要额外处理,可以加在这里。
/
如果用eclipse编译相应的project后,进入bin路径;
把ndk安装路径export 到你的PATH环境变量中。例如你的NDK安装路径:/home/android-ndk-sdk/android-ndk-r8b,下一步设置NDK=/home/android-ndk-sdk/android-ndk-r8b,export NDK; 然后export PATH=$NDK:$PATH
然后执行:javah org.eshock.jnitest.JNITest
在Android中,我们如果编译了一个APK,可以使用javah 自动生成相应的JNI的C实现头文件。但是,跟单独使用javac编译文件不同的时候,我们需要额外指定android.jar库的路径。否则会出现奇怪的错误。下面是两个典型的错误。
当我们编译完毕后,使用 javahorg.eshock.jnitest.JNITest,如果会出现如下错误:
Error: Could not find class file for ‘org.eshock.jnitest.JNITest’.
解决办法:
javah -classpath /root/workspace/HelloWorld/bin/classesorg.eshock.jnitest.JNITest
请注意,由于我们Android编译后,会将.class文件放到classes目录下面,如果我们在bin目录下,执行,还会报上面的错误的。这里非常关键。
当我们使用上面的方法编译后,出现如下错误:
Error: cannot access android.app.Activity
class file for android.app.Activity not found
解决方法,附加android.jar库的地址:
javah -classpath /opt/android-sdk-linux/platforms/android-8/android.jar:/root/workspace/HelloWorld/bin/classesorg.eshock.jnitest.JNITest
至此,我们就会生成文件org_eshock_jnitest_JNITest.h
或者如果当那个JAVA是通用版本,首先随便在某个目录下创建一个文件JNITest.java:
public class JNITest {
public native int plus (int x, int y);
}
先用javac JNITest.java生产.class文件,然后用javah 生成.h文件
然后在命令行下执行:
javac JNITest.java && javah JNITest
将生成一个文件JNITest.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */
#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest
* Method: plus
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_JNITest_plus
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
这里自动生成了java中native函数对应的签名,你可以在c代码中使用它。(记得修改包名,把JNICALL Java_JNITest_plus改成Java_org_eshock_jnitest_JNITest_plus)
//
接下来,我们就可以根据定义C部分。在project目录下建立一个jni目录,进入jni目录,建立下面的.c文件,同时把上面javah生成的.h文件拷贝
到jni路径
接下来我们使用C语言实现这个plus函数。
创建<project>/jni/mylib.c:
#include <string.h>
#include <jni.h>
JNIEXPORT jint JNICALL
Java_org_eshock_jnitest_JNITest_plus( JNIEnv* env,
jobject thiz,
jint x,
jint y )
{
return x + y;
}
这里还有一个问题需要注意的就是:
编写Android.mk文件我们使用NDK-Build生成的文件是libhelloworld.so,我们在使用System.loadLibrary的时候,参数只需要到helloworld,而不需要把lib和.so加上
//一般都是这行
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
//代码文件
LOCAL_SRC_FILES:=mylib.c
//包含的头文件
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
//生成的so库名称
LOCAL_MODULE := mylib
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
//
include $(BUILD_SHARED_LIBRARY)其中LOCAL_PATH表示c源代码文件的位置;LOCAL_MODULE表示生成的共享库的名称;LOCAL_SRC_FILES代表c代码的文件。不需要把头文件列在里面;头文件的依赖关系是ndk自动计算的。
//
最后执行ndk-build。如果是虚拟机则要ndk-build APP_ABI=x86
这样就在project目录下面自动生成了libs和obj目录