尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦。NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用Java的引用类型则要做对应的处理。

一、对引用数据类型的操作

尽管Java的引用类型的实如今NDK被屏蔽了,JNI还是提供了一组API,通过JNIEnv接口指针提供原生方法改动和使用Java的引用类型。

1、字符串操作

JNI把Java的字符串当作引用来处理,在NDK中使用Java的字符串,须要相关的API进行转换。JNI支持Unicode编码和UTF-8编码的字符串,有两组函数通过JNIEnv接口指针处理这些字符串编码:

jstring javaString;
javaString = (*env)->NewStringUTF(env, "Hello World");

该方法生成一个的UTF-8编码字符串。

2、Java字符串转C字符串

要在原生方法中使用Java字符串,须要将Java字符串转成C字符串。能够调用GetStringChars函数:

const jbyte *str;
jboolean isCopy; str = (*env)->GetStringUTFChars(env, javaString, &isCopy);

第三个參数isCopy,能够用作推断该函数返回的字符串是否是Java字符串的副本,还是直接指向Java字符串的内存。

3、释放字符串

通过JNIEnv调用的GetStringChars和GetStringUTFChars函数获得的C字符串在使用完后要正确释放,否则就会造成内存泄漏。可通过ReleaseStringUTFChars函数(用于释放Unicode)和ReleaseStringUTFChars函数(用于释放UTF-8)释放字符串。

(*env)->ReleaseStringUTFChars(env, javaString, str);
(*env)->ReleaseStringChars(env, javaString, str);

二、数组操作

JNI把Java的数组也是当作引用类型处理的,只是JNI还是提供了函数操作Java数组的。

1、创建数组

直接用New<Type>Array函数能够创建数组实例。Type能够是原生数据类型,也能够是Object,使用对应的API传递參数确定大小。

jintArray array;
array = (*env)->NewIntArray(env, 10);
if (0 == array) {
// do it
}

2、訪问数组

JNI有两种方式能够訪问Java数组,能够将数组的代码复制成C数组,然后再操作C数组,完毕后提交改动,这样效率有点低。另外一办法是让JNI直接提供指向数组元素的指针。

方法一:使用副本,调用Get<Type>ArrayRegion函数复制,Set<Type>ArrayRegion函数提交改动

// 将Java数组拷贝到C数组
jintArray javaArray;
jint array[10]; // ... // 复制数组
(*env)->GetIntArrayRegion(env, javaArray, 0, 10, array); // do it // 提交改动
(*env)->SetIntArrayRegion(env, javaArray, 0, 10, array);

当数组非常大的时候,这种方法的效率就非常低。

方法二:直接操作指针,调用Get<Type>ArrayElements函数获取数组的指针

jint *array;
jboolean isCopy;
jintArray javaArray; // ... array = (*env)->GetIntArrayElements(env, javaArray, &isCopy);

第三个參数isCopy的作用同Java字符串转C字符串,是否为Java数组的副本。

使用完之后,就要立即释放,否则会造成内存泄漏,释放函数是Release<Type>ArrayElements

(*env)->ReleaseIntArrayElements(env, javaArray, array, 0);

第三个參数0代表将内容复制回来并释放原生数组。假设是JNI_COMMIT,则复制回来,但不释放。JNI_ABORT,释放但不复制回来。

三、NIO操作

JNI提供NIO操作函数,使Java能够使用的原生代码创建的缓冲区。相比数组操作,NIO缓冲区的传输数据性能更好,适合在原生代码和Java应用之间传输大量数据。

1、创建字节缓冲区,使用NewDirectByteBuffer方法

unsigned char *buff = (unsigned char *) malloc(1024);

// ...

jobject directBuff;
directBuff = (*env)->NewDirectByteBuffer(env, buffer, 1024);

须要注意的是,原生方法的内存分配不在虚拟机的管理范围,所以须要手动管理内存避免内存泄漏。

2、获取Java字节缓冲区

Java也能够创建字节缓冲区,在原生方法中调用GetDirectBufferAddress函数获取原生字节数组的内存地址

unsigned char *buff;
jbyteArray directBuffer; // ... buffer = (unsigned char *) (*env)->GetDirectBufferAddress(env, directBuffer);

四、訪问域

原生方法想要获取Java的成员变量和调用Java的成员函数,就要通过JNI提供的訪问域方法。

Java有两种域,各自是实例域和静态域。类的对象有个自己实例域的副本,而类一个的全部对象共用同一个静态域。有一下Java类,JavaClass:

public class JavaClass {

	private String instanceField = "instance filed";

	private static String staticField = "static filed";

	private String getInstanceField() {
return instanceField;
} private static String getStaticField() {
return staticField;
}
}

1、获取域ID

JNI通过域ID来訪问两种域,能够通过实例的获取class对象,然后获取域ID,使用GetObjectClass函数能够获得class对象

jclass clazz;
jobject instance; // ... clazz = (*env)->GetObjectClass(env, instance);

依据域的类型不同,使用GetFieldId函数获取实例域ID,GetStaticFieldId获取静态域ID,返回类型均为jfieldID;

jfieldID fieldId;

// 获取实例域ID
fieldId = (*env)->GetFieldID(env, clazz, "instanceField", "Ljava/lang/String;"); // 获取静态域ID
fieldId = (*env)->GetStaticFieldID(env, clazz, "staticField", "Ljava/lang/String;");

两个函数的最后一个參数是Java中表示域类型的域描写叙述符。

能够缓存最频繁使用的域ID,这样能够提高性能。

2、获取域

获取域ID之后,就能够通过Get<Type>Field函数来获取实例域,通过GetStatic<Type>Field获取静态域

jstring field;

// 获取实例域
field = (*env)->GetObjectField(env, instance, fieldId); // 获取静态域
field = (*env)->GetStaticObjectField(env, clazz, fieldId);

获取一个Java域的值就要调用两到三个JNI函数,很麻烦,并且效率也比較低,建议把须要的參数传递给原生方法,这样能够提高性能。

四、调用方法

1、获取方法ID

和域一样,Java的方法有两类,訪问这两类方法,先要获取方法的ID,使用GetMethodID和GetStaticMethodID,返回值类型为jmethodID。

jmethodID methodId;

// 获取实例方法ID
methodId = (*env)->GetMethodID(env, clazz, "getInstanceField", "()Ljava/lang/String;"); // 获取静态方法ID
methodId = (*env)->GetStaticMethodID(env, clazz, "getStaticField", "()Ljava/lang/String;");

两个方法的最后一个參数表示的方法描写叙述符,在Java中表示方法签名。

2、调用方法

以方法ID为參数通过调用Call<ReturnType>Method和CallStatic<ReturnType>Method函数调用方法。

jstring result;

// 调用实例方法
result = (*env)->CallStringMethod(env, instance, methodId); // 调用静态方法
result = (*env)->CallStaticStringMethod(env, clazz, methodId);

Java方法和原生代码的转换代价比較大,建议在类设计的时候要规划好,这样才干提高性能。

五、域和方法描写叙述符

使用Java的成员变量和方法,都必须通过域描写叙述符号和方法描写叙述符来获取域和方法的ID。Java类型的签名映射关系例如以下:

Boolean -> Z

Byte -> B

Char -> C

Short -> S

Int -> I

Long -> J

Float -> F

Double -> D

其他类 -> L + 类名(包名用’\‘分隔)

type[] -> [type

方法 -> (參数类型签名) + 返回类型签名

可见使用起来相当麻烦。

关于Java与原生代码之间的通信,假设发生了内存泄漏,API就会返回NULL,崩溃的时候假设没有抛出异常,那么原生代码就会停止执行,应用程序会发生闪退。

Android NDK开发篇(五):Java与原生代码通信(数据操作)的更多相关文章

  1. Android NDK开发篇:Java与原生代码通信(数据操作)

    虽然说使用NDK可以提高Android程序的执行效率,但是调用起来还是稍微有点麻烦.NDK可以直接使用Java的原生数据类型,而引用类型,因为Java的引用类型的实现在NDK被屏蔽了,所以在NDK使用 ...

  2. Android NDK开发篇:Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.访问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次详细分析一 ...

  3. Android NDK开发篇:Java与原生代码通信(异常处理)

    一.捕获异常 异常处理是Java中的功能,在Android中使用SDK进行开发的时候经常要用到.Android原生代码在执行过程中如果遇到错误,需要检测,并抛出异常给Java层.执行原生代码出现了问题 ...

  4. Android NDK开发篇(四):Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一 ...

  5. Android NDK开发篇(六):Java与原生代码通信(异常处理)

    一.捕获异常 异常处理是Java中的功能.在Android中使用SDK进行开发的时候常常要用到.Android原生代码在运行过程中假设遇到错误,须要检測,并抛出异常给Java层.运行原生代码出现了问题 ...

  6. Android NDK开发(五)--C代码回调Java代码【转】

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/41862479 在上篇博客里了解了Java层是怎样传递数据到C层代码,并且熟悉了大部 ...

  7. Android NDK开发三:java和C\C++交互

    转自:http://www.cnblogs.com/shangdahao/archive/2013/05/02/3053971.html 1.定义native方法并加载动态链接库: public cl ...

  8. Android NDK开发篇:如何使用JNI中的global reference和local reference

    JNI提供了一些实例和数组类型(jobject.jclass.jstring.jarray等)作为不透明的引用供本地代码使用.本地代码永远不会直接操作引用指向的VM内部的数据内容.要进行这些操作,必须 ...

  9. Android NDK 开发(四)java传递数据到C【转】

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处 ...

随机推荐

  1. ViewPager中的子Activity无法响应OnActivityResult的解决方法

    ViewPager子Activity通过startActivityForResult()跳转至OtherActivity,OtherActivity回传结果由ViewPager所在的父Activity ...

  2. DotNetOpenAuth实践之搭建验证服务器

    系列目录: DotNetOpenAuth实践系列(源码在这里) DotNetOpenAuth是OAuth2的.net版本,利用DotNetOpenAuth我们可以轻松的搭建OAuth2验证服务器,不废 ...

  3. Rookey.Frame之实体FluentValidation验证

    昨天给大家介绍了Rookey.Frame框架的实体设计,今天继续跟大家分享实体的FluentValidation验证,在Rookey.Frame框架中可以设置多种验证方式:FluentValidati ...

  4. 关于日志API接口中流量的使用。

    现状: 目前服务器使用带宽是2M,那么最大上行流量应该是250kb/s,而通过日志发现目前最大并发流量是350kb/s. 问题: 看到以上的结果时,我当时的疑问是最大并发流量超过了服务器最大上行流量, ...

  5. CSUOJ 1901 赏赐 OR 灾难 单调栈

    Description 大G南征北战终于打下了大片土地成立了G国,大G在开国大典上传召帮助自己南征北战的三大开国元勋小A,小B,小C进殿,并要赏赐三人大量宝物以显示天恩浩荡.大G在征服其他国家的时候抢 ...

  6. 1035 Password (20)(20 point(s))

    problem To prepare for PAT, the judge sometimes has to generate random passwords for the users. The ...

  7. with上下文管理器

    术语 要使用 with 语句,首先要明白上下文管理器这一概念.有了上下文管理器,with 语句才能工作. 下面是一组与上下文管理器和with 语句有关的概念. 上下文管理协议(Context Mana ...

  8. mysql高性能索引

    独立索引: 独立索引是指索引列不能是表达式的一部分,也不能是函数的参数 例1: SELECT actor_id FROM actor WHERE actor_id+1=5 --这种写法,就算在acto ...

  9. PreEssentials与MFC集成使用

    ProEssentials是Gigasoft公司开发的一个功能十分强大的分发免费的工控图表.它提供了几乎所有的曲线显示形式,支持多种开发工具,提供以下接口供开发者调用:.NET(WinForm).AS ...

  10. ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

    登录服务器,使用root用户连接mysql时出现错误提示: $ bin/mysql -uroot -p Enter password: ERROR (HY000): Can't connect to ...