其实JNI和NDK区别可以这样理解:JNI是一套SUN的API,而NDK更像一个工具,它是GOOGLE自己提供的,编译C/C++的
一: 关于JNI:
JNI即 Java native intereface,为Java应用程序提供调用本地方法的接口,JNI的首要目标在以库文件的形式调用本地方法,在WIndows下为DLL,在UNIX下为SO。
缺陷方面:本来java程序运行在JVM上可以做到平台无关,但是如果Java程序通过JNI调用原生的代码(C/C++等),则丧失了平台无关性。
优势:复用C/C++代码,调用本地库。
注意: 在J2me上是没有JNI,不能像JNI那样调用外部的DLL。假如你需要使用某DLL,也只能在"设计"J2ME虚拟机的时候静态链接加进去,前提是你能够弄到Symbian的JAVA虚拟机的源代码,然后把DLL加到虚拟机里,并且能够替换掉原Symbian的JAVA虚拟机。。(摘自优快云某位)
二:JNI的使用
JNI程序开发的一般操作步骤如下:
1:编写java中的调用类:设计思想:将本地方法集中在单个类中,以便将以后所需的移植工作减到最少。
2:用javah生成c/c++原生函数的头文件,javah 不使默认内部命令,需要指明路径
3:c/c++中调用需要的其他函数功能,实现原生函数
4:将项目依赖的所有原生库和资源加入到java项目的java.library.path
5:发布Java和动态库(DLL/so放在Jar包的同一级目录下)
三:注意事项
1:使用javah生成.h文件时:UnsatisfiedLinkError+方法名错误
2:在一个Applet应用中,不要使用 JNI。因为在 applet 中可能引发安全异常。
3:将所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
4:本地方法要简单。尽量将生成的 DLL 对任何第三方运行时 DLL 的依赖减到最小。使本地方法尽量独立,以将加载 DLL 和应用程序所需的开销减到最小。如果必须要运行时 DLL,则应随应用程序一起提供它们。
5:本地代码运行时,没有有效地防数组越界错误、错误指针引用带来的间接错误等。所以必须保证保证本地代码的稳定性,因为,丝毫的错误都可能导致 Java 虚拟机崩溃。
四:JNI数据类型映射
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv*env, jobject obj){}
JNIEXPORT和JNICALL是在jni.h中的宏定义。确保这个函数在本地库外可见,并且C编译器会进行正确的调用转换。C/C++函数的名字构成:Java+包名+类名+函数名。与javah指令生成的.h文件的函数名对应,如果函数名不正确会导致UnsatisfiedLinkError。
JNI把JAVA中的对象当作一个C指针传递到本地方法中,这个指针指向JVM中的内部数据结构,而内部数据结构在内存中的存储方式是不可见的。本地代码必须通过在JNIEnv中选择适当的JNI函数来操作JVM中的对象。
第一个参数JNIEnv接口指针,指向一个个函数表,函数表中的每一个入口指向一个JNI函数。本地方法经常通过这些函数来访问JVM中的数据结构。
第二个参数根据本地方法是一个静态方法还是实例方法而有所不同。本地方法是一个静态方法时,第二个参数代表本地方法所在的类(jclass);本地方法是一个实例方法时,第二个参数代表本地方法所在的对象(jobject)。
对应基本数据类型:
JNI对基本类型和引用类型的处理是不同的。基本类型的映射是一对一的。例如JAVA中的int类型直接对应C/C++中的jint。
对于引用类型:
JNI把JAVA中的对象当作一个C指针传递到本地方法中,这个指针指向JVM中的内部数据结构,而内部数据结构在内存中的存储方式是不可见的。本地代码必须通过在JNIEnv中选择适当的JNI函数来操作JVM中的对象。例如String对应jstring,本地代码只能通过GetStringUTFChars这样的JNI函数来访问
Java类型 | 本地类型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++带符号的8位整型 |
char | jchar | C/C++无符号的16位整型 |
short | jshort | C/C++带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
Class | jclass | Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
函数 | Java数组类型 | 本地类型 |
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
函数 | 描述 |
GetFieldID | 得到一个实例的域的ID |
GetStaticFieldID | 得到一个静态的域的ID |
GetMethodID | 得到一个实例的方法的ID |
GetStaticMethodID | 得到一个静态方法的ID |
Java 类型 | 符号 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects对象 | Lfully-qualified-class-name;L类名 |
Arrays数组 | [array-type [数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
不能直接访问JNI里面的本地类型,要通过对应函数访问。
JNI支持字符串在Unicode和UTF-8两种编码之间转换。支持C/C++两套API
//C 格式
(*env)-><jni function>( env, <parameters> )
//C++ 格式
env-><jni function>( < parameters> )
JNI API为了避免丑陋的函数名,提供了方法向Java虚拟机注册函数映射表。这样当Java调用Native接口的时候,Java虚拟机就可以不用根据函数名来决定调用哪个函数了,直接通过查询表格就可以找到需要调用的函数了。
从本地代码中调用Java方法。也就是通常说的来自本地方法中的callbacks(回调)。
native/java和java/native和java/java效率比较:native/java<java/native<java/java
五:JNI中的引用
JNI支持三种引用:局部引用、全局引用、弱全局引用
局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
局部引用或者全局引用会阻止GC回收它们所引用的对象,而弱引用则不会。
不是所有的引用可以被用在所有的场合。例如,一个本地方法创建一个局部引用并返回后,再对这个局部引用进行访问是非法的。
1:局部引用:
局部引用只有在创建它的本地方法返回前有效。本地方法返回后,局部引用会被自动释放。(static局部对象也是一样)
实际上JNI中的局部引用和C语言中局部变量是不同的,他的有效期是当前Native函数被调用的上下文中。我理解的调用上下文,为Java虚拟机的调用流程。Native函数是被Java虚拟机调用的,Native函数执行完成之后,控制流程将继续返回给Java虚拟机。局部变量在Native函数中,由Native代码调用Java虚拟机的JNI接口创建,秉着谁创建谁销毁的原则,当Native函数执行完成之后,如果局部引用没有被Native代码显示删除,那么局部引用在Java虚拟机中还是有效的。Java虚拟机来决定在什么时候来删除这个对象。这和C语言的局部变量概念是不同的。这也可以解释为什么Natvie函数能够以一个局部引用为返回值了。
局部引用在Native代码显示释放非常重要。
(如果你实现的Native函数是工具函数,会被频繁的调用。如果你在Native函数中没有显示删除局部引用,那么每次调用该函数Java虚拟机都会创建一个新的局部引用,造成局部引用过多。尤其是该函数在Native代码中被频繁调用,代码的控制权没有交还给Java虚拟机,所以Java虚拟机根本没有机会释放这些局部变量。)
JNIapi中有三个函数对局部引用进行处理:EnsureLocalCapacity、PushLocalFrame、PopLocalFrame
可以用栈来理解局部变量。
2:全局引用:
全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被GC回收。NewGlobalRef和DeleteGlobalRef.
3:弱引用
弱引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象。NewWeakGlobalRef和DeleteWeakGlobalRef。使用的时候应该检查它的有效性
每一个JNI引用被建立时,除了它所指向的JVM中的对象以外,引用本身也会消耗掉一个数量的内存。作为一个JNI程序员,应该对程序在一个给定时间段内使用的引用数量十分小心。短时间内创建大量不会被立即回收的引用会导致内存溢出。
六:DLL与so相关
NDK:
一:NDK简介
Native Development Kit。NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。Google提高的NDK中API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)
二:使用cygwin
下载安装cygwin,cygwin是cygwin是一个在windows平台上运行的unix模拟环境。
具体参考:百度文库这篇:点这里。
三:Android.mk文件和Makefile
EOE上能找翻译文档。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE
:= helloworld
LOCAL_SRC_FILES := helloworld.c
include $(BUILD_SHARED_LIBRARY)