JNI和NDK的学习总结
来源:互联网 发布:您的网络存在安全风险 编辑:程序博客网 时间:2024/06/04 18:33
一、概述
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 := helloMokuaiLOCAL_SRC_FILES := hello.cinclude $(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");}@Overrideprotected 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 += -llogLOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llogLOCAL_MODULE := helloMokuaiLOCAL_SRC_FILES := hello.cinclude $(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");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void click2(View v){logFromC();}//定义一个本地方法,该方法输出logcat日志,用于c代码的debugpublic 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");}@Overrideprotected 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 := helloMokuaiLOCAL_SRC_FILES := hello.cppinclude $(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");}@Overrideprotected 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值为0int 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
- JNI和NDK的学习总结
- NDK 和 JNI 总结
- Android JNI和NDK学习(06)--JNI的数据类型
- Android JNI和NDK学习(07)--JNI的常用API
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- JNI和NDK的区别
- NDK和JNI的使用
- NDK和JNI的区别?
- NDK 和 jni 的关系
- JNI和NDK的关系
- 关于android ndk的jni总结
- Android JNI和NDK学习(01)--搭建NDK开发环境
- 一道亚马逊算法面试题的情景分析
- 垂直居中应用
- 转载-------为什么要内存对齐 Data alignment: Straighten up and fly right
- 算法系列博客之写在前面的话
- orbslam2-基础理论(4)词袋
- JNI和NDK的学习总结
- hadoop:基于Streaming实现作业提交
- MongoDB备份与恢复
- 构建大数据产品-目录
- 基于JQuery的Ajax
- android 适配性完全攻略
- 1-4-2实例代码
- Android性能优化之被忽视的Memory Leaks
- 自定义ListView的下拉刷新控件