JNI高阶知识总结
来源:互联网 发布:淘宝返利api接口源码 编辑:程序博客网 时间:2024/05/17 04:29
JNI与NDK的关系
NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。
JNIEnv与JavaVM
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
JNIEnv 与 JavaVM : 注意区分这两个概念;
– JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
– JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
JNIEnv 作用 :
– 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
– 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
JNIEnv 体系结构
线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;
*.so的入口函数
JNI_OnLoad()与JNI_OnUnload()
当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。它的用途有二:
(1)告诉VM此C组件使用那一个JNI版本。如果你的.so档没有提供JNI_OnLoad()函数,VM会默认该.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
(2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。
JNI字符串函数
常用的JNI函数将在后续介绍,这里给出其中的字符串操作函数的函数名以及相关描述。
GetStringChars
ReleaseStringChars 获得/释放一个Unicode格式的字符串指针,可能返回一个字符串的副本
GetStringUTFChars
ReleaseStringUTFChars 获得/释放一个UTF-8格式的字符串指针,可能返回一个字符串的副本
GetStringLength 返回Unicode格式字符串的长度
GetStringUTFLength 返回UTF-8格式字符串的长度
NewString 根据Unicode格式的C字符串创建一个Java字符串
NewStringUTF 根据UTF-8格式的C字符串创建一个Java字符串
GetStringCritical
ReleaseStringCritical 获得/释放一个Unicode格式的字符串指针,可能返回一个字符串的副本【在该函数对区间内,不能使用任何JNI函数】
GetStringRegion 将Unicode格式的String复制到预分配的缓冲区中
GetStringUTFRegion 将UTF-8格式的String复制到预分配的缓冲区中
int sprintf( char *buffer, const char *format, [ argument] … );
类似于printf,根据格式化字符串format,将后续参数列表中的参数逐个输出。不过输出目标不是标准输出终端,而是字符串buffer。
字符串操作
C字符串——>java字符串
例如:下面的函数以一个C字符串为参数,并返回一个Java字符串引用类型jstring值。
jstring javastringjavastring = (*env)->NewStringUTF(env, "I LOVE YOU !");
注意,在内存溢出的情况下,NewString函数将返回NULL以通知原生代码虚拟机中有异常抛出。
java字符串转换成C字符串
为了在原生代码中使用java字符串,需要先将java字符串转换成C字符串,我们使用GetStringChars函数可以将Unicode格式的java字符串转换成C字符串,使用GetStringUTFChars函数可以将UTF-8格式的Java字符串转换成C字符串。这些函数的第三个参数均为可选参数,该可选参数名是isCopy,它让调用者确定返回的C字符串地址指向副本还是指向堆中的固定对象。例如:
const jbyte* str;jboolean isCopy;str = (*env)->GetStringUTFChars(env, javaString,&isCopy);if(0 != str){ printf("java String: %s",str); if(JNI_TRUE == isCopy){ printf("C String is a copy of the java String"); }else{ printf("C String points to actual String"); }}
释放字符串
通过JNI GetStringChars 函数GetStringUTFChars函数获得的C字符串在原生代码中使用完成之后需要正确的释放,否则将会引起内存泄漏。通常我们使用ReleaseStringChars函数释放Unicode格式的字符串,使用ReleaseUTFStringChars函数释放UTF-8格式的字符串.
(*env)->ReleaseUTFStringChars(env,javaString,str);
数组操作
JNI把java数组当成引用类型来处理,JNI提供必要的函数访问和处理Java数组。
创建数组
用NewArray函数在原生代码中创建数组实例,其中可以是Int、Char和Boolean等。例如:
jintArray javaArray;javaArray = (*env)->NewIntArray(env,10);if(0 != javaArray){/*数组使用……*/}
注意,在内存溢出的情况下,NewArray函数将返回NULL以通知原生代码虚拟机中有异常抛出。
访问数组元素
JNI提供两种访问java数组元素的方法,可以将数组的代码赋值成C数组或者让JNI提供直接执行数组元素的指针。
对副本的操作
1.java数组转C数组
GetArrayRegion函数将给定的基本Java数组赋值到给对你给的C数组中,例如:
jint nativeArray[10];(*evn)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);
2.C数组转java数组
原生代码可以像使用普通的C数组一样使用和修改数组元素。当原生代码想将所做的修改提交给java数组时,可以使用SetArrayRegion函数将C数组复制回java数组中。例如:
(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);
注意:当数组很大时,对数组做复制操作会引起性能问题。
对直接指针的操作
3.java数组转C数组
原生代码可以使用GetArrayElements函数获取执行数组元素的直接指针。例如:
jint nativeDirectArray;jboolean isCopy;nativeDirectArray = (*env)->GetIntArrayElements(env,javaArray,&isCopy);
其中,第三个&isCopy参数为可选参数,让调用者确定返回的C字符串地址指向副本还是指向堆中的固定对象。 因为可以像普通的C数组一样访问和处理数组元素,因此JNI没提供访问和处理数组元素的方法,JNI要求原生代码用完这些指针后立刻释放,否则会出现内存溢出。可以使用JNI提供的ReleaseArrayElements函数释放GetArrayElements返回的C数组。例如:
(*env)->ReleaseIntArrayRegion(env,javaArray,nativeDirectArray,0);
其中第四个参数是释放模式。 释放模式动作0将内容复制回来并释放原生数组JNI_COMMIT将内容复制回来但是不释放原生数组,一般用于周期性的更新一个java数组JNI_ABORT释放原生数组但不用将内容复制回来
NIO操作
JNI提供了在原生代码中使用NIO(I/O)的函数,与数组操作相比更适合原生代码和java应用程序之间传送大量数据。
创建直接字节缓冲区
原生代码可以创建java应用程序使用的直接字节缓冲区,该过程是以提供一个原生C字节数组为基础,例如:
unsigned char* buffer = (unsigned char*) malloc(1024)……jobject directBuffer;directBuffer = (*env)->NewDirectByteBuffer(env,buffer,1024);
直接字节缓冲区获取
java应用程序中也可以创建直接字节缓冲区,在原生代码中调用GetDirectBufferAddress函数可以获取原生自己数组的内存地址。例如:
unsigned char* bufferbuffer = (unsigned char*) (*env)->GetDirectBufferAddress(env,directBuffer);
JNI访问java对象属性
// 实例域private String instanceField = "Instance Field ";// 静态域private static String staticField = "Static Field ";
获取域ID
JNI提供了用域ID访问两类域的方法,可以通过给定实例的class对象获取域ID,用GetObjectClass函数可以获得class对象,例如:
jclass clazzclazz = (*env)->GetObjectClass(env,instance);
1.使用GetFieldID获取实例域的ID
jfieldID instanceFieldId;instanceField = (*env)->GetFieldID(env,clazz,"instanceFieldId","Ljava/lang/String;");
2.使用GetStaticFieldID获取静态域的ID
jfieldID staticFieldId;staticFieldId = (*env)->GetStaticFieldID(env,clazz,"staticFieldId","Ljava/lang/String;");
获取域
在获得域ID之后,可以用GetField函数获得实际的实例域,例如:
1.获得实例域
jstring instanceFieldId;instanceField = (*env)->GetObjectField(env,clazz,"instanceFieldId");
2.获得静态域
jfieldID staticField;staticFieldId = (*env)->GetStaticObjectField(env,clazz,"staticFieldId");
两个函数的最后一个参数是java中表示域类型的域描述符,其中”Ljava/lang/String;”表明域类型是Sting。
JNI调用Java方法
public class WJavaClass{// 实例方法private String instanceMethod(){return "Instance Method";}// 静态方法private static String staticMethod(){return "StaticMethod";}}
获取方法ID
JNI提供了用方法ID访问两类方法的途径,可以用给定实例的class对象获得方法ID。用GetMethodID函数获得实例方法的方法ID,例如:
jmethodID instanceMethodId;instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");
用GetStaticMethodID函数获得静态域的方法ID,例如:
jmethodID staticMethodId;staticMethodId = (*env)->GetStaticMethodID(env,clazz,"staticMethod","()Ljava/lang/String;");
两个函数的最后一个参数均表示方法描述符,在Java中表示方法签名。
调用方法
可以以方法ID为参数通过CallMethod类函数调用实际的实例方法,例如:
1.调用实例方法
jstring instanceMethodResult;instanceMethodResult = (*env)->CallStringMethod(env,instance,"instanceMethodId");
2.调用静态方法
jstring staticMethodResult;staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,"staticMethodId");
JNI调用Java静态方法案例
public class HelloJni extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); callJavaStaticMethod(); } public native String callJavaStaticMethod(); static { System.loadLibrary("hello-jni"); } // 静态方法 private static String staticMethod() { return "StaticMethod Castiel"; }}
#include <string.h>#include <jni.h>#include <android/log.h>JNIEXPORT void JNICALLJava_com_example_hellojni_HelloJni_callJavaStaticMethod(JNIEnv *env, jclass type) { jclass jniClass = (*env)->FindClass(env, "com/example/hellojni/HelloJni"); if (NULL == jniClass) { __android_log_print(ANDROID_LOG_INFO,"HelloJni","can't find jclass"); return; } jmethodID getMId = (*env)->GetStaticMethodID(env, jniClass, "staticMethod", "()Ljava/lang/String;"); if (NULL == getMId) { __android_log_print(ANDROID_LOG_INFO,"HelloJni","can't find method getStringFromStatic from JniClass"); return; } jstring result = (*env)->CallStaticObjectMethod(env, jniClass, getMId); const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL); (*env)->DeleteLocalRef(env, jniClass); (*env)->DeleteLocalRef(env, result); __android_log_print(ANDROID_LOG_INFO,"HelloJni",resultChar);
JNI异常处理
调用throwingMethod方法时,accessMethod原生方法需要显示地做异常处理。JNI提供了ExceptionOccurred函数查询虚拟机中是否有挂起的现象。例如,原生代码中的异常处理:
jthrowable ex;……(*env)->CallVoidMethod(env,instance,throwingMethodId);ex = (*env)->ExceptionOccurred(env);if(0 != ex){(*env)->ExceptionClear(env);/*Exception handler*/}
抛出异常
public class WJavaClass{// 抛出方法private void throwingMethod() throws NullPointerException{throw new NullPointerException("Null Pointer");}}
JNI也允许原生代码抛出异常。因为异常是java类,应该先用FindClass函数找到异常类。用ThrowNew函数可以初始化且抛出新的异常,例如:
jclass clazz;……clazz = (*env)->FindClass(env,"java/lang/NullPointerException");if(0 !=clazz){(*env)->ThrowNew(env,clazz,"Exception message");}
JNI的局部引用和全局引用和弱全局引用
局部引用
大多数JNI函数返回局部引用。局部引用不能在后续的调用中被缓存及重用,主要是因为它们的使用期限仅限于原生方法,一旦原生函数返回,局部引用即被释放。例如,使用FindClass函数返回一个局部引用,当原生方法返回时,它被自动释放,也可以用DeleteLocalRef函数显示释放原生代码:
jclass clazzclazz = (*env)->FindClass(env,"java/lang/String");……(*env)->DeleteLocalRef(env,clazz);
根据JNI的规范,虚拟机应该允许原生代码创建最少16个局部引用
全局引用
全局引用在原生方法的后续调用过程中依然有效,除非它们被原生代码显示释放。
1.创建全局引用
可以用NewGlobalRef函数将局部引用初始化为全局引用,例如:
jclass localclazzjclass globalclazz……localclazz = (*env)->FindClass(env,"java/lang/String");globalclazz = (*env)->NewGlobalRef(env,localclazz );…… (*env)->DeleteLocalRef(env,localclazz );
2.删除全局引用
当原生代码不再需要一个全局引用时,可以随时用DeleteLocalRef函数释放它。
(*env)->DeleteLocalRef(env,globalclazz );
弱全局引用
弱全局引用和全局引用一样,在原生方法的后续调用过程中依然有效。与全局引用不同,弱全局引用并不阻止潜在的对象被垃圾回收。
1.创建弱全局引用
用NewWeakGlobalRef函数对弱全局引用进行初始化,例如:
jclass weakGlobalclazzweakGlobalclazz = (*env)->NewWeakGlobalRef(env,localclazz);
2.弱全局引用的有效性校验
可以使用IsSameObject函数检验一个弱全局引用是否仍然指向活动的类实例,例如:
if(JNI_FALSE == (*env)->IsSameObject(env,weakGlobalClazz,NULL)){/*对象仍然处于活动状态且可以使用*/}else{/*对象被垃圾回收期收回,不能使用*/}
删除弱全局引用
可以随时使用DeleteWeakGlobalRef函数释放弱全局引用。
(*env)->DeleteLocalRef(env,weakGlobalClazz);
JNI常用函数大全
http://blog.csdn.net/qinjuning/article/details/7595104
- JNI高阶知识总结
- JNI高阶知识总结
- JNI高阶知识总结
- Android JNI知识总结
- JNI知识总结
- 高性能知识总结
- 高可用性知识总结
- JNI的知识总结(全)
- Linux高阶知识
- [JNI] Android JNI总结
- C/C++基础及高频率面试知识总结
- JNI总结
- JNI总结
- jni总结
- jni总结
- JNI 总结
- Android JNI知识简介
- Android JNI知识简介
- HTDP学习笔记(2)--习题2.2.1之我的答案
- 安装Git
- 设计模式之6大设计原则
- 基于Java实现的基本二叉树
- java八大基本类型
- JNI高阶知识总结
- oracle--PLSQL
- Muduo之Channel源码解析
- strlen函数的三种写法
- SEAndroid kernel 源码解析2--策略执行
- mina 添加心跳包
- 冒泡排序
- PHP中system()、exec()输出错误信息
- 一般框架min.js 与js 有什么区别,如jquery.min.js与jquery.js有什么区别