JNI和NDK的学习总结

来源:互联网 发布:您的网络存在安全风险 编辑:程序博客网 时间:2024/06/04 18:33

一、概述

    1JIN定义:JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。

    2NDK定义:Android NDKNative Development Kit)是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。

二、NDK目录结构

    docs:帮助文档

    build/toolsLinux批处理文件

    platforms:存放开发jni用到的h头文件和so动态链接库

    prebuilt:预编译使用的工具

    sample:使用jni的案例

    sourceNDK的部分源码

    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中配置NDKWindows-->Preferences-->Android-->NDK,然后指定NDK Location,比如D:\Java\android-ndk-r10

四、用法和示例

    示例1java里面调用C的函数

       1在项目下创建一个文件夹命名为jni,在该文件夹下创建.cAndroid.mk文件。另外一种方式是:右键项目-->Android tools-->Add Native support会自动生成jni文件夹以及该文件夹下的Android.mk和指定的cpp文件。


    2查阅文档(文档里面有大量的解释说明)。位置在D:\Java\android-ndk-r10\docs\Programmers_Guide\html\index.html

    3C类型与Java类型转换,请看jni.h文件。文件在D:\Java\android-ndk-r10\platforms\android-L\arch-arm\usr\include\jni.h


    4、先在MainActivityJava代码里面指定本地函数:

       例如: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 supporthello.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文件。如果项目添加了本地支持则不需要手动编译。

    7java代码中调用。

       <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*);

  参数1JNIEnv指针

  参数2jclass反射类对象

  参数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>

 

  示例4Java调用C++函数

1、新建Android项目,添加本地支持,导入头文件路径,自动生成jni文件夹以及Android.mkhello.cppApplication.mk文件

1Android.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>


2MainActivity.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文件中

1cppc的区别

<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进程

1fork分支出来的进程在运行的过程中更难被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


五、参考资料

1JNI实战全面解析 :http://blog.csdn.net/banketree/article/details/40535325

2、配置eclipse自动编译jni文件夹下的c文件的方式:给项目添加本地支持,本文有介绍

3、解决eclipsewindows-->preferences-->Android下无法显示ndk选项:http://jingyan.baidu.com/article/4d58d5413000a09dd4e9c0fe.html















0 0
原创粉丝点击