先介绍环境:
window:win7 32位 jdk1.7
ubuntu 11.04 64位 jdk1.6 ndk-r8b
JNI(Java Native Interface)----java本地接口,它的好处是:允许java代码在java虚拟机里面相互操作使用其他语言(例如C、C++、汇编等等)编写的类库或者应用程序.
什么时候用:当你的应用程序用java编写的时候没有办法完成所有的功能的时候,就要用到JNI了.(比如你需要在应用层驱动底层的硬件工作)
在此先介绍使用javah工具、arm-linux-gcc等工具生成.so的共享库文件,提供给上层的android应用.(需要window和ubuntu环境)
1.先在eclipse中新建一个NdkC的android工程,修改MainActivity.java如下
package com.undergrowth.ndkc; import android.os.Bundle; import android.app.Activity; import android.widget.TextView; public class MainActivity extends Activity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView=(TextView) findViewById(R.id.text); ArithOpera add=new ArithOpera(); int result=add.add(10, 20); //调用本地方法 textView.append(" 10+20="+result); } }
提供本地算术运算的ArithOpera.java如下
package com.undergrowth.ndkc; /* * 提供一个本地方法 计算两数之和 */ public class ArithOpera { //本地加法方法 public native int add(int a,int b); //加载共享库名为libarith.so //遵循Unix的习惯 前缀lib 和后缀.so都不加 系统会自动加上 static{ System.loadLibrary("arith"); } }
2.使用javah工具生成含有本地方法的头文件
在cmd下进入到上面NdkC的目录下,如我的
输入javah -classpath ./bin/classes/ com.undergrowth.ndkc.ArithOpera
就会在NdkC的目录下生成含有本地方法的头文件com_undergrowth_ndkc_ArithOpera.h
内容许下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_undergrowth_ndkc_ArithOpera */ #ifndef _Included_com_undergrowth_ndkc_ArithOpera #define _Included_com_undergrowth_ndkc_ArithOpera #ifdef __cplusplus extern "C" { #endif /* * Class: com_undergrowth_ndkc_ArithOpera * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_undergrowth_ndkc_ArithOpera_add (JNIEnv *, jobject, jint, jint); #ifdef __cplusplus } #endif #endif
对上面的文件简单解释一下:
#include <jni.h> 包含jni.h头文件 jni.h头文件声明/定义了在c语言中使用的数据类型 接口函数 宏之类的 位于 Javajdk1.7.0_01include目录下
#ifndef _Included_com_undergrowth_ndkc_ArithOpera
#define _Included_com_undergrowth_ndkc_ArithOpera
#endif
这两句还有最后面的一个#endif,用于保证头文件在预编译阶段只被包含一次
#ifdef __cplusplus
extern "C" {
#endif
.....
#ifdef __cplusplus
}
#endif
这几句代码是说如果是c++源文件,则使用c语言的方式进行编译和链接
中间的
/*
* Class: com_undergrowth_ndkc_ArithOpera
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_undergrowth_ndkc_ArithOpera_add(JNIEnv *, jobject, jint, jint);
是利用jni本地方法的命名规则生成的一个方法声明,规则如下
3.上面的操作都是在window环境下,接着在ubuntu11.04环境下使用交叉编译器生成.so文件
将com_undergrowth_ndkc_ArithOpera.h拷贝到ubuntu环境中的 在com_undergrowth_ndkc_ArithOpera.h的同一目录中新建add.c文件 内容如下:
#include <jni.h> #include "com_undergrowth_ndkc_ArithOpera.h" JNIEXPORT jint JNICALL Java_com_undergrowth_ndkc_ArithOpera_add(JNIEnv *env,jobject obj,jint a,jint b) { return (a+b); }
使用arm-linux-gcc进行编译 arm-linux-gcc -I ~/java/jdk1.6.0_30/include/ -I ~/java/jdk1.6.0_30/include/linux/ -fPIC -c add.c -o add.o
上面命令生成add.o的目标文件
-I 用于指点add.c中用到的jni.h头文件的位置 第二个-I是因为jni.h中包含jni_md.h 而jni_md.h则在第二个位置
-fPIC(位置独立代码)用于指定生成与位置无关的代码,即在内存的任意地方都可以运行
上面生成add.o的文件后,在使用arm-linux-ld进行链接,生成.so的文件 arm-linux-ld -share add.o -o libarith.so
即生成了libarith.so的共享库 将之拷贝到NdkC的libs/armeabi/目录下 没有该目录则创建即可 该目录名不可为其他的
4.运行 效果如下
现在介绍使用ndk的方式达到上面的一样效果.下面的操作都是在ubuntu 11.04的环境中
先对ndk做个简单的介绍:ndk相当于是jni的超集,
主要做这么两件事: a.利用Android.mk生成一个jni兼容的共享库
b.将生成的共享库复制到你项目的libs/armeabi/目录下
1.在eclipse中新建一个ndk1的android工程,将Ndk1Activity.java修改如下
package com.undergrowth.ndk; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class Ndk1Activity extends Activity { /** Called when the activity is first created. */ private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView=(TextView) findViewById(R.id.text); textView.append(" 10+20="+add(10,20)); } public native int add(int a,int b); static{ System.loadLibrary("arith"); } }
2.然后再ndk1的项目文件夹中新建jni目录(与src目录在同一级别),在jni中新建add.c源文件 内容如下:
#include <jni.h> jint Java_com_undergrowth_ndk_Ndk1Activity_add(JNIEnv *env,jobject thiz,jint a,jint b) { return (a+b); }
同样使用上面提到的jni本地方法的命名规则 以Java_ 开头 接着是类的全名 接着是方法名 都用_(下划线)分隔
同时在jni的目录中新建Android.mk文件 用于告诉ndk的编译系统编译什么 如何编译 内容如下:
LOCAL_PATH :=$(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE :=arith LOCAL_SRC_FILES :=add.c include $(BUILD_SHARED_LIBRARY)
上面的Android.mk文件主要涉及到NDK的三个概念:
a.模块变量-----用于向编译系统描述你的模块
如LOCAL_PATH-----用于定位源代码在开发者目录中的位置
LOCAL_MODULE----用于定义你要生成的模块
LOCAL_SRC_FILES-----用于告诉编译系统你要编译的源文件
b.宏函数---必须要使用$(call <function name>)的方式使用,并且返回文本信息
如$(call my-dir)-----my-dir----用于返回当前文件的目录
c.ndk提供的变量-----具有特定的功能
include $(CLEAR_VARS)-----CLEAR_VARS---指向一个gnu的特殊脚本文件,用于清除(LOACL_XXX)变量,LOCAL_PATH除外,因为所有的编译控制文件都会被解析成一个全局文件,而全局文件里面的所有变量均为全局的
include $(BUILD_SHARED_LIBRARY)------编译成共享库,即.so的后缀
3.进行编译,进入到ndk1的目录中,使用ndk-build(前提是你以前配置好了ndk的环境),会有如下信息,并会在ndk1的目录中的libs/armeabi/下找到libarith.so文件
u1@u1:~/java/workspace/ndk1$ ndk-build Compile thumb : arith <= add.c SharedLibrary : libarith.so Install : libarith.so => libs/armeabi/libarith.so
4.运行
上面即是jni与ndk操作的简单实现.其实还可以使用JNI_OnLoad的方式进行编写,在之前的使用中,我倒是两种方式都用,用JNI_OnLoad主要是我觉得可以自己在C组件中定义函数名,只需要通过RegisterNatives进行注册即可,即把C组件中的函数与上层应用的函数进行一一的映射.
JNI_OnLoad的具体使用,参见: http://blog.csdn.net/undergrowth/article/details/9163745