1、JNI简介
JNI是Java调用Native机制,是Java语言自己的特性全称为Java Native Interface,JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
2、Java通过JNI机制和C/C++沟通的具体步骤
1)、编写包含native本地方法的java类
2)、通过javah工具生成C/C++语言的头文件
3)、使用C/C++语言实现头文件
4)、使用交叉编译工具对C/C++本地代码进行编译,最后通过链接生成*.so可执行的C/C++库
5)、实际执行Java代码去和本地的C/C++代码互相沟通
3、Android的NDK
Android的NDK(Native Development Kit)开发工具集是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例代码。通过Android的NDK,Android软件开发者可以很方便地实现Java和本地C/C++代码的相互调用,充分发挥本地硬件特性和C/C++高效性。
4、开发第一个Android NDK程序
1)、编写包含native本地方法的java类
创建一个工厂名称为HelloAdroidNDK的Android工程,在此工程中创建一个NativeClass.java文件,在此文件中加载本地的C库文件,同时声明本地的C方法。
package com.igood.ndk.hello;
public class NativeClass {
//静态代码块在类加载时会执行,这个时候就会加载本地的C库文件
static{
//加载本地的C库文件
System.loadLibrary("helloAndroidNDK");
}
//声明加载的C库中的本地方法
public native String sayHelloToNDK();
}
保存文件后,Eclipse自动帮我们完成该类的编译工作,此时会在本工程下的bin\classes\com\igood\ndk\hello路径下生成NativeClass.class字节码文件。
2)、通过javah工具生成C/C++语言的头文件
在HelloAdroidNDK工程目录下建立一个名为jni的文件夹,打开Windows的命令窗口,进入到HelloAdroidNDK工程目录下,使用javah工具产生NativeClass.class的头文件具体命令如下图:
此时我们成功地执行了javah命令并生成了头文件,打开jni文件夹,可以看到自动生成的头文件com_igood_ndk_hello_NativeClass.h,具体内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_igood_ndk_hello_NativeClass */
#ifndef _Included_com_igood_ndk_hello_NativeClass
#define _Included_com_igood_ndk_hello_NativeClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_igood_ndk_hello_NativeClass
* Method: sayHelloToNDK
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_igood_ndk_hello_NativeClass_sayHelloToNDK
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
3)、使用C/C++语言实现头文件
此时我们可以新建一个com_igood_ndk_hello_NativeClass.c文件,实现相应的函数功能
#include "com_igood_ndk_hello_NativeClass.h"
JNIEXPORT jstring JNICALL Java_com_igood_ndk_hello_NativeClass_sayHelloToNDK
(JNIEnv *env , jobject thiz)
{
return (*env)->NewStringUTF(env,"Hello Android NDK!!");
}
4)、使用交叉编译工具对C/C++本地代码进行编译,最后通过链接生成*.so可执行的C/C++库
编写Android.mk编译脚本文件,主要指定生成的so模块名称,所要编译的c文件和所要链接的其他C库,内容如下图:
打开Windows的命令窗口,进入到HelloAdroidNDK工程目录下,执行ndk-build命令就可以编译,,在obj\local\armeabi\目录下生成libhelloAndroidNDK.so库文件。
5)、实际执行Java代码去和本地的C/C++代码互相沟通
修改MainActivity.java的内容,调用NativeClass类中声明的本地函数,修改后内容如下:
package com.igood.ndk.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView tvMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NativeClass nc = new NativeClass();
tvMsg = (TextView) findViewById(R.id.tv_msg);
tvMsg.setText(nc.sayHelloToNDK());
}
}
运行的界面如下图所示:
文本内容显示了从本地C代码中获得字符串的内容,说明成功调用本地的C语言代码。
5、JNI中的JavaVM与JNIEnv对象
在标准的Java平台下,每个Process里可以产生很多JavaVM对象,每个JavaVM对象都有一个与之对应的JavaVM对象,但是在Android平台上,每个Process只能产生一个DalvikVM对象,也就是说在一个Android的进程中是通过有且只有一个虚拟器对象来服务所有Java和C++代码的。
1、JNIEnv *内部包含一个Pointer,Pointer指向Dalvik的JavaVM对象的Fanction Table,JNIEnv *关于程序执行环境的众多函数正是来源于Dalvik虚拟机
2、Android中每当一个Java线程第一次要调用本地C/C++代码时,Dalvik虚拟机实例会为该Java线程产生一个JNIEnv *指针
3、Java每条线程在和C/C++互相调用时,JNIEnv*是相互独立的,互不干扰
4、每本地的C/C++代码想获得当前线程所要使用的JNIEnv时,可以使用Dalvik VM对象的Java VM* jvm->getEnv()方法,该方法即会返回当前线程所在的JNIEnv*。
6、在Android的NDK中,Java、C/C++、Dalvik VM关系如下:
1、java的dex字节码和C/C++的*.so同时运行DalvikVM之内,共同使用一个进程空间。每次使用jni调用c/c++开辟一个线程去处理
2、java和C/C++可以相互调用,调用的关键是DalvikVM
3、一般而言,比较经典的模式是Java通过JNI的C组建和C++相互沟通,一般业务处理放在C/C++中
4、C++代码处于核心控制地位更具价值
当java需要C/C++代码时,在DalvikVM虚拟机中加载动态链接库时,会先调用JNI_Onload()函数,此时就会把javaVM对象的指针存储于C层JNI组建的全局环境中,在JAVA层调用C层的本地库函数时,调用C本地函数线程必然通过Dalvik VM来调用C本地函数,测试Dalvik虚拟机会为本地的C组建实例化一个JNIEnv指针,该指针指向Dalvik虚拟机的具体函数列表,当JNI的C组件调用java层方法和属性时,需要通过JNIEnv指针来进行调用。
当C++组件主动调用Java层方法时,需要通过JNI的C组件把JNIEnv指针传递给C++组件,此后,c++组件即可通过JNIEnv指针来掌控Java层代码。