一、概述
1、JIN定义:JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。
2、NDK定义:Android NDK(Native Development Kit)是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
二、NDK目录结构
docs:帮助文档
build/tools:Linux批处理文件
platforms:存放开发jni用到的h头文件和so动态链接库
prebuilt:预编译使用的工具
sample:使用jni的案例
source:NDK的部分源码
toolchains:工具链
三、安装NDK工具
1、下载NDK,依次点击下图。http://developer.android.com/sdk/ndk/index.html
官网可能被墙了,提供1个可用的地址
http://www.cnblogs.com/yaotong/archive/2011/01/25/1943615.html
2、将下载的NDK压缩包解压之后,把D:\Java\android-ndk-r10文件夹路径配置到环境变量中。
3、在eclipse中配置NDK,Windows-->Preferences-->Android-->NDK,然后指定NDK Location,比如D:\Java\android-ndk-r10
四、用法和示例
示例1:java里面调用C的函数
1、在项目下创建一个文件夹命名为jni,在该文件夹下创建.c和Android.mk文件。另外一种方式是:右键项目-->Android tools-->Add Native support会自动生成jni文件夹以及该文件夹下的Android.mk和指定的cpp文件。
2、查阅文档(文档里面有大量的解释说明)。位置在D:\Java\android-ndk-r10\docs\Programmers_Guide\html\index.html
3、C类型与Java类型转换,请看jni.h文件。文件在D:\Java\android-ndk-r10\platforms\android-L\arch-arm\usr\include\jni.h
4、先在MainActivity的Java代码里面指定本地函数:
例如:public native String helloFromC();
再去生成头文件,jdk1.7是在命令行中进入到项目的src目录而jdk1.6是在命令行进入到项目的bin/classes目录下输入:javah调用本地函数的类的全路径名,例如:javah com.example.helloworld.MainActivity<回车>,之后就会在src目录(或者bin/classes目录)生成一个头文件,里面包含我们需要的本地函数名,然后复制这个函数名到.c文件里面就变成了本地方法。
5、如果项目添加的本地支持(也就是右键项目-->Android tools-->Add Native support)hello.c里面的头文件需要指定路径,否则报错。指定方式:右键项目-->Properties-->C/C++ General-->Paths and Symbols,然后点击add-->点击FileSystem,然后依次找到头文件所在的路径,比如:D:\Java\android-ndk-r10\platforms\android-L\arch-arm\usr\include。添加本地支持的好处:
<1>自动生成jni文件夹
<2>自动生成c文件和Android.mk文件
<3>指定jni.h头文件的路径,相当于关联源码
<4>不需要再去jni目录下使用ndk-build指令,项目部署时,会先打包编译so类库再去部署到手机上。
hello.c源代码
<span style="font-size:18px;">#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
//没有main方法的,因为是由java执行的
//定义一个函数实现本地方法:helloFromC(),这里的函数参数自动传进来的,调用时不用写参数
//env参数:结构体二级指针,该结构体中封装了大量的函数指针,可以帮助程序员实现某些常用功能
//this参数:本地方法调用者的对象(MainActivity的对象)
jstring Java_com_example_jni_MainActivity_helloFromC(JNIEnv *env, jobject this){
char *cStr = "hello from c";
//把C字符串转换成java字符串
//函数原型:jstring (*NewStringUTF)(JNIEnv* ,const char*);
jstring jStr = (*env)->NewStringUTF(env,cStr);
return jStr;
}</span>
Android.mk源代码
<span style="font-size:18px;">LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := helloMokuai
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)</span>
application.mk源代码:(只有一行)
<span style="font-size:18px;">APP_ABI := all</span>
6、手动编译这些.c文件。在.c文件夹下按住shift+鼠标右键,选择“在此处打开命令窗口”。输入ndk-build,回车即可在项目目录下libs/armeabi/ libhelloMokuai.so的.so文件。如果项目添加了本地支持则不需要手动编译。
7、在java代码中调用。
<1>首先要加载编译好的模块名
<span style="font-size:18px;">static{
<span style="white-space:pre"> </span>System.loadLibrary("helloMokuai");
}</span>
<2>声明这个本地C语言方法
//定义一个本地方法,本地方法没有方法体,由本地语言实现。
public native String helloFromC();
<3>然后调用这个本地C语言方法
String msg = helloFromC();
Toast.makeText(this, msg, 0).show();
MainActivity.java源代码:
<span style="font-size:18px;">package com.example.jni;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
//加载动态链接库,写模块名
static{
System.loadLibrary("helloMokuai");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
Toast.makeText(this, helloFromC(), 0).show();
}
//定义一个本地方法,本地方法没有方法体,由本地语言实现。
public native String helloFromC();
}</span>
8、效果:
9、注意事项
(1)、出现如下情况的解决:
原因:没有指定头文件的路径,不过这个不是错误,不影响程序的运行,只是不能通过Ctrl+点击查看源代码。指定方式(添加本地支持才可以指定):右键项目-->Properties-->C/C++ General-->Paths and Symbols,然后点击add-->点击FileSystem,然后依次找到头文件所在的路径,比如:D:\Java\android-ndk-r10\platforms\android-L\arch-arm\usr\include。
示例2:在C代码中使用logcat输出
1、修改Android.mk
如生成的库文件是“.so文件”,则在Android.mk中添加如下内容:
LOCAL_LDLIBS:=-L$(SYSROOT)/usr/lib -llog
如生成的库文件是“.a文件”,则在Android.mk中添加如下内容:
LOCAL_LDLIBS:=-llog
2、使用方式一:
<span style="font-size:18px;">#include<android/log.h>
#define LOG_TAG “msg”
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__ )
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__ )
然后调用的时候:LOGD(“我是debug的消息”);</span>
<span style="font-size:18px;">#include <android/log.h>
__android_log_write(ANDROID_LOG_INFO,"Tag","111111111111");
__android_log_print(ANDROID_LOG_INFO,"Tag","222222222222");</span>
源码:
Android.mk:
<span style="font-size:18px;">LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#LOCAL_LDLIBS += -llog
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_MODULE := helloMokuai
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)</span>
hello.c:
<span style="font-size:18px;">#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#define TAG "msg"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
JNIEXPORT void JNICALL Java_com_example_jni_MainActivity_logFromC(JNIEnv *env, jobject thiz){
LOGD("这是C里面的方法输出的");//这里好像不行
LOGI("这是C里面的方法输出的");
/**
* ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
*/
__android_log_write(ANDROID_LOG_INFO,"Tag","111111111111");
__android_log_print(ANDROID_LOG_INFO,"Tag","222222222222");
}</span>
MainActivity.java:
<span style="font-size:18px;">package com.example.jni;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
//加载动态链接库,写模块名
static{
System.loadLibrary("helloMokuai");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click2(View v){
logFromC();
}
//定义一个本地方法,该方法输出logcat日志,用于c代码的debug
public native void logFromC();
}</span>
示例3:在C代码中调用Java的方法
1、步骤1:加载字节码
函数原型:jclass (*FindClass)(JNIEnv*, const char*);
2、步骤2:读取方法
函数原型:jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
参数1:JNIEnv指针
参数2:jclass反射类对象
参数3:方法名称
参数4:方法签名(用javap指令在Windows命令行创建)
返回值:jmethodID
创建Java方法签名的方式:在项目的bin/classes目录下输入:javap -s 包名和类名 例如:javap -s com.example.jni.MainActivity。然后得到方法的签名。
3、步骤3:调用方法
函数原型:void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
源码:
hello.c:
<span style="font-size:18px;">#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
JNIEXPORT void JNICALL Java_com_example_jni_MainActivity_methodFromC
(JNIEnv *env, jobject thiz){
//1、加载字节码
jclass clazz = (*env)->FindClass(env,"com/example/jni/MainActivity");
//2、读取方法
/**
* 函数原型 jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
* 参数1:JNIEnv指针
* 参数2:jclass反射类对象
* 参数3:方法名称
* 参数4:方法签名
* 返回值:jmethodID
*/
jmethodID methodId = (*env)->GetMethodID(env,clazz,"showInJava","(Ljava/lang/String;)V");
//3、运行方法
(*env)->CallVoidMethod(env,thiz,methodId,(*env)->NewStringUTF(env,"这是C代码调用Java方法时输入的消息"));
}</span>
MainActivity.java
<span style="font-size:18px;">package com.example.jni;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
//加载动态链接库,写模块名
static{
System.loadLibrary("helloMokuai");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click3(View v){
methodFromC();
}
//java方法
public void showInJava(String msg){
AlertDialog.Builder builder = new Builder(this);
builder.setTitle("提示").setMessage("C调用的Java代码").show();
}
//下面这个是本地方法,在本地方法里调用Java方法showInJava()
public native void methodFromC();
}</span>
示例4:Java调用C++函数
1、新建Android项目,添加本地支持,导入头文件路径,自动生成jni文件夹以及Android.mk、hello.cpp、Application.mk文件
(1)Android.mk:
<span style="font-size:18px;">LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := helloMokuai
LOCAL_SRC_FILES := hello.cpp
include $(BUILD_SHARED_LIBRARY)</span>
2、MainActivity.java
<span style="font-size:18px;">package com.example.jni;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
//加载动态链接库,写模块名
static{
System.loadLibrary("helloMokuai");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
String str = logFromCPP();
Toast.makeText(this,”收到的信息”+str,0).show();
}
//定义一个C++本地方法
public native void logFromCPP();
}</span>
3、用javah生成头文件,复制本地方法名到hello.cpp文件中
(1)cpp和c的区别
<1>C++本地函数的参数env是一级指针,直接调用其他函数。C本地函数的参数env是二级指针,调用其他函数时要带上“*”。
<2>C++中的函数要先声明才能使用,所以要导入javah生成的头文件。而C不用声明则不需要导入javah生成的头文件。
hello.cpp的内容如下:
<span style="font-size:18px;">#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
//双引号表示当前目录,尖括号表示在编译器目录下--------------------
#include “com_example_cplusplus_MainActivity.h” //---
// ---------------------------------------------------------------
jstring Java_com_example_jni_MainActivity_helloFromCPP(JNIEnv *env, jobject this){
char *cStr = "hello from cpp";
//把C字符串转换成java字符串
jstring jStr = *env->NewStringUTF(cStr);
return jStr;
}</span>
示例5、用fork()新建C进程
1、fork分支出来的进程在运行的过程中更难被kill,因此一般用来执行一些特殊的代码,比如应用卸载发送统计数据功能时需要用到fork进程在应用卸载后依然运行,然后发送卸载数据到服务器进行统计卸载量。
2、使用fork的创建的函数代码如下:
<span style="font-size:18px;">JNIEXPORT void JNICALL Java_com_example_jni_MainActivity_forkFromC(JNIEnv *env, jobject thiz){
//分支c进程,返回一个整型的进程id
//子进程分支出来后,会把c代码又执行一次,但是不会在fork新的进程了,第2次返回进程id值为0
int pid = fork();
if(pid < 0){
__android_log_print(ANDROID_LOG_INFO,"msg","分支失败!");
}
else if(pid == 0){
//如果pid=0,说明代码执行在子进程
__android_log_print(ANDROID_LOG_INFO,"msg","pid = 0");
<span style="white-space:pre"> </span>//在这里放置fork分支的进程要执行的代码。
//此时运行while不会阻塞主进程
//while(1){
//}
}
else if(pid > 0){
//如果pid>0,说明代码执行在主进程
__android_log_print(ANDROID_LOG_INFO,"msg","pid = %d", pid);
}
}</span>
本文案例源码下载(可能会提示错误,个别地方改一下就能用)http://download.csdn.net/detail/sq_bang/9586513
五、参考资料
1、JNI实战全面解析 :http://blog.csdn.net/banketree/article/details/40535325
2、配置eclipse自动编译jni文件夹下的c文件的方式:给项目添加本地支持,本文有介绍
3、解决eclipse的windows-->preferences-->Android下无法显示ndk选项:http://jingyan.baidu.com/article/4d58d5413000a09dd4e9c0fe.html