JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信,JNI框架使得本地方法可以访问Java对象,就如同Java程序访问这些本地对象。本地方法可以创建Java对象,然后检查、使用这些对象执行任务。本地方法也可以检查并使用由Java程序创建的对象。
原生方法
1.原生方法的声明 如下面程序所示,helloFromJNI方法声明中含有关键词native以通知Java编译器,它是用另一种语言提供该方法的具体实现。原生方法没有方法体,在这里只是一个声明。
/** *  原生方法由“hello-jni”原生库实现 */public native String helloFromJNI();


2.加载共享库 原生方法被编译成一个共享库,我们需要先加载该共享库以便虚拟机能够找到原生方法的实现。java.lang.System类提供了两个静态方法,load() 和 loadLibrary() 用于在运行时加载共享库。
/** * 启动时加载“hello-jni”库 */static {     System.loadLibrary("hello-jni");}


loadLibaray 的参数不包含共享库的位置,Java库路径,也就是系统属性java.library.path保存loadLibrary方法在共享库搜索的目录列表,Android上的Java库路径包含/vendor/lib和/system/lib。 在loadLibaray在扫描Java库路径时,一旦发现同名的库,立即加载共享库。因为Java库路径的第一组目录是Android系统目录,为了避免与系统库命名冲突,建议为每一个共享库选择唯一的命名。
3.实现原生方法
hello-jni.c 源文件:
#include <jni.h>#include <string.h>Jstring Java_com_example_hellojni_HelloJni_helloFromJNI(JNIEnv* env,jobject thiz){     return (*env)->NewStringUTF(env,"Hello JNI!");}


原生方法helloFromJNI 用了一个名为Java_com_example_hellojni_helloFromJNI 的完全限定的函数来声明,这种显示的函数命名让虚拟机在加载的共享库中自动查找原生函数。
尽管Java方法helloFromJNI 不带任何参数,但是原生方法带两个参数env和thiz。第一个参数JNIEnv是指向JNI函数表的接口指针;第二个参数jobject是HelloJni类实例的Java对象引用。

JNIEnv接口指针 原生代码通过JNIEnv 接口指针提供的各种函数来使用虚拟机的功能。JNIEnv 是一个指向线程-局部数据的指针,而线程-局部数据中包含指向函数表的指针。实现原生方法的函数将JNIEnv接口指针作为它们的第一个参数。
传递给每一个原生方法调用的JNIEnv 接口指针在方法调用相关的线程中也是有效,但是它不能被缓存以及被其它线程使用。
原生代码 C 与 C++调用JNI函数的语法不同。C 代码中JNIEnv 是指向JNINativeInterface 结构的指针,为了访问任何一个JNI函数,该指针需要首先被解引用。因为C 代码中的JNI 函数不了解当前的JNI 环境,JNIEnv 实例应该作为第一个参数传递给每一个JNI 函数调用者,例如:
     return (*env)->NewStringUTF(env,"Hello JNI!");

在C++代码中,JNIEnv 实际上C++ 类实例,JNI函数以成员函数的形式存在。因为JNI 方法已经访问了当前的JNI 环境,因此JNI 方法调用不要求JNIEnv 实例参数。例如:
     return env->NewStringUTF("Hello JNI!");


实例方法与静态方法 Java 程序设计语言有两类方法:实例方法和静态方法。实例方法与类实例相关,它们只能在类的实例中调用。静态方法不与类实例相关,它们可以在静态上下文直接调用。静态方法和实例方法均可以声明为原生的,可以通过JNI 技术以原生代码的形式提供它们的实现。
原生实例方法通过第二个参数获取实例引用,该参数是jobject 类型的,例如:
  JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_helloFromJNI(JNIEnv* env,object thiz);


原生静态方法,因为静态方法没有与实例绑定,因此通过第二个参数获取类引用而不是实例引用,第二个参数值是jclass 类型,例如:
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_helloFromJNI(JNIEnv* env,jclass clazz);

数据类型 基本类型 Java 中的基本类型可以直接在jni中映射:
引用类型 引用类型与基本类型不一样,引用类型对原生方法是不透明的,它们的内部结构不直接向原生代码公开。下面就是引用类型映射表: 数据类型的操作 引用类型以不透明的引用方式传递给原生代码,而不是以原生数据类型的方式呈现,因此它不能直接使用和修改,JNI提供了与这些引用类型密切相关的一组API,这些API通过JNIEnv接口指针提供给原生函数。
1.字符串操作 JNI 把Java字符串当成引用类型来处理。这些引用类型并不像原生 C 字符串那样可以直接使用,JNI 提供了Java 字符串与 C 字符串之间相互转化的必要函数。Java 字符串对象在这里是不可变的,因此 JNI 不提供任何修改现有的 Java 字符串内容的函数。
JNI 支持 Unicode 和 UTF-8 编码格式的字符串,还提供了两组函数通过JNIEnv 接口指针处理这些字符串编码。 1.1创建字符串: NewStringUTF 函数构建 UTF-8 编码的字符串实例: jstring javaString = (*env)->NewStringUTF(env,"Hello JNI!"); NewString 函数构建 Unicode 编码格式的字符串实例: jstring javaString = (*env)->NewString(env,"Hello JNI!");

Java 字符串转换为 C 字符串 GetStringChars 和GetStringUTFChars 函数分别将Unicode 和UTF-8 格式的Java字符串转换为 C 字符串。 Jboolean isCopy; const jbyte* str = (*env)->GetStringChars(env,JavaString,&isCopy); ..... const jbyte* strUtf = (*env)->GetStringUTFChars(env,JavaString,&isCopy);
上面两个函数的第三个参数是可选参数,它让调用者确定返回的 C 字符串地址指向副本还是堆中的固定值。
1.2释放字符串 通过上面GetStringChars 和GetStringUTFChars 函数获得的 C 字符串在原生代码中使用完以后需要正确的释放掉,否则将会引起内存泄露。 (*env)->ReleaStringChars(env,javaString,str); (*env)->ReleaStringUTFChars(env,javaString,strUft);

2.数组操作 JNI 把 Java 数组当成引用类型来处理, JNI 提供必要的函数访问和处理 Java 数组。
2.1创建数组 用 New<Type>Array 函数在原生代码里面创建数组实例,其中<Type>可以是 Int、Char、Boolean等,例如 NewIntArray、NewCharArray、NewBooleanArray。在创建数组的时候需要给出数组的大小。 jintArray javaArray = (*env) -> NewIntArray(env,10);
与 NewString 函数一样,在内存溢出的情况下, New<Type>Array 函数将返回 NULL ,以通知元素代码虚拟机中有异常抛出。
2.2访问数组元素 JNI 提供了两种访问 Java 数组元素的方法,可以将数组元素复制成 C数组,或者让 JNI 提供直接指向数组元素的指针。 Get<Type>ArrayRegion 函数将给定的基本 Java 数组复制到指定的 C 数组中,如下程序:
jint nativeArray[10]; (*env) -> GetIntArrayRegion(env,javaArray,0,10,nativeArray);
原生代码可以像使用普通的 C 数组一样使用和修改数组元素。当原生代码想将所做的修改提交给 Java 数组时,用 Set<Type>ArrayRegion 函数将 C 数组复制回 Java 数组中,如下代码:
(*env) -> SetIntArrayRegion(env,javaArray,0,10,nativeArray);
当数组很大的时,用上面两个方法复制整个数组会引起性能问题。这种情况下,原生代码应该只对某个区域的数据进行操作而不是整个数组。
Get<Type>ArrayElements 函数获取指向数组元素的直接指针。如下程序: jboolean isCopy; jint* nativeDirectArray = (*env) -> GetIntArrayElements(env,javaArray,&isCopy);
nativeDirectArray 可以像普通的 C 数组一样访问和处理数组元素。第三个参数是可选参数,和上面字符串操作一样,它让调用者确定返回的 C 字符串地址指向副本还是堆中的固定值。 JNI 要求原生代码在使用完这些指针立即释放,否则会出现内存溢出。原生代码使用 Release<Type>ArrayElements 函数释放掉上面获取到的 C 数组指针。代码如下: (*env) -> ReleaseIntArrayElements(env,javaArray,nativeDirectArray,0);
该函数的第四个参数是表示释放模式。 0 将内容负责回来并释放原生数组。 JNI_COMMIT 将内容复制回来但是不释放原生数组,一般用于周期性的更新一个 Java 数组。 JNI_ABORT 释放原生数组但不用将内容复制回来

转载声明:http://blog.csdn.net/jmq_0000/article/details/38800557






更多相关文章

  1. Android自定义控件之自定义属性
  2. Android(安卓)的 intent (手抄)
  3. Android(安卓)ActivityManagerService(AMS)的启动分析
  4. Android之——任意时刻从子线程切换到主线程的实现(插曲)
  5. Android(安卓)----SQLiteDate(Cursor接口)
  6. Android(安卓)sax解析XML数据
  7. React Native带你从源码解决启动白屏(Android)
  8. Java与C之间传递数据
  9. Android(安卓)自定义 View 之 onMeasure() 源码分析及重写

随机推荐

  1. 基础篇--ES集群部署
  2. 基础篇--ES基础数据操作
  3. Kibana的安装及配置应用
  4. ELK收集Nginx日志
  5. rsync+inotify实现服务器的实时同步
  6. linux终端命令
  7. PyCharm激活码,亲测有效(支持最新PyCharm20
  8. sh nginx 虚拟主机安装卸载
  9. 从“会写程序的干不过会写PPT的”聊程序
  10. 用户故事驱动的敏捷开发 – 1. 规划篇