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

尽管说使用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与原生代码通信(数据操作),布布扣,bubuko.com

时间: 2024-08-02 06:49:16

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

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

Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一下.依据javah自己主动生成的头文件.能够看出原生方法的声明和定义,比如: JNIEXPORT jstring JNICALL Java_com_shamoo_helljni_HelloJni_stringFromJNI(JNIEnv *, jobject); 虽然Java上的原生方法没有不论什么

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

一.捕获异常 异常处理是Java中的功能,在Android中使用SDK进行开发的时候经常要用到.Android原生代码在执行过程中如果遇到错误,需要检测,并抛出异常给Java层.执行原生代码出现了问题,例如使用了空指针.内存泄漏,并且没有做相应的检测盒异常抛出,APP会马上闪退,没有任何提示. JNI中的异常处理和Java的不一样.Java中的异常处理,是直接捕获,然后做相应的处理.JNI要求开发人员在异常发生之后显式实现异常处理流.例如以下例子: public class JavaClass

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

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41862479 在上篇博客里了解了Java层是怎样传递数据到C层代码,并且熟悉了大部分的实际开发知识,基本上掌握这些就可以做一个基本的NDK开发了,但是光是了解Java回调C层的数据是不是还不够啊,考虑问题要考虑可逆性,Java能回调C,那么C能否反过来回调Java呢?答案是肯定可以的,这篇博客就介绍一个C语言如何调用Java层的代码.以下是一些问题场景,我们带着这个问题场景来分析

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

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层“协议”在Java和C之间起个“桥梁”的作用,将Java和Native C之间联立起来,让Java

Android NDK开发(四)——Java传递数据到C

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层"协议"在Java和C之间起个"桥梁"的作用,将Java和Nativ

android NDK开发在本地C/C++源码中设置断点单步调试具体教程

近期在学android NDK开发,折腾了一天,最终可以成功在ADT中设置断点单步调试本地C/C++源码了.网上关于这方面的资料太少了,并且大都不全,并且调试过程中会出现各种各样的问题,真是非常磨人.程序员就得有耐心. 把自己的调试过程记录下来.希望对须要的朋友有帮助. 在看本文之前,请先确保你已经成功编译了一个android NDKproject,而且可以在模拟器或者真机上执行.至于怎么编译NDKproject,包含配置.生成.so文件等等.可以參考我的前一篇博客:http://blog.cs

Android NDK开发初识

神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育网站,我初步了解了NDK的神秘面纱,好东西自然要分享,接下来我们就一起来认识一下Android NDK开发. 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三

windows下用ADT进行android NDK开发的详细教程(从环境搭建、配置到编译全过程)

郑重申明:如需转载本博客,请注明出处,谢谢! 这几天在学习android NDK的开发,那么首先让我们来看看android NDK开发的本质是什么. NDK(Native Development Kit),即本地开发工具,简单地说,就是在开发android应用程序的时候,在java类中调用native函数,而native函数的接口也是在java类中定义的,但是native函数最终由本地的C/C++代码实现.简单地说,就是在java中调用C/C++函数.至于为什么要用NDK,我总结了一下,大致有以

如何定位Android NDK开发中遇到的错误

做android应用的调试,最怕就是报错,crash,看到这篇好文章,记录一下: 转自:http://www.csdn.net/article/2014-12-30/2823366-Locate-Android-NDK Android NDK是什么? Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”.众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代