JNI和NDK的学习总结

一、概述

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

    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");
	}
	@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*);

  参数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");
	}
	@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>


 

  示例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    := helloMokuai
LOCAL_SRC_FILES := hello.cpp
include $(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");
	}
	@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文件中

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值为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


五、参考资料

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















评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值