您的位置:首页 > 移动开发 > Android开发

Android jni编程浅入深出之-- 与原生代码通信

2014-08-24 22:05 260 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: