现在的位置: 首页 > 综合 > 正文

JNI和Android NDK的使用

2013年09月04日 ⁄ 综合 ⁄ 共 4849字 ⁄ 字号 评论关闭

其实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虚拟机。。(摘自CSDN某位)

二: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中有三个函数对局部引用进行处理:EnsureLocalCapacityPushLocalFramePopLocalFrame

可以用栈来理解局部变量。

 

 

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)

抱歉!评论已关闭.