虽然说使用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 = (*env)->NewStringUTF(env, "Hello World");

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

2、Java字符串转C字符串

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

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

第三个参数isCopy,可以用作判断该函数返回的字符串是否是Java字符串的副本,还是直接指向Java字符串的内存。

3、释放字符串

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

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

二、数组操作

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

1、创建数组

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

  1. jintArray array;
  2. array = (*env)->NewIntArray(env, 10);
  3. if (0 == array) {
  4. // do it
  5. }

2、访问数组

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

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

  1. // 将Java数组复制到C数组
  2. jintArray javaArray;
  3. jint array[10];
  4. // 复制数组
  5. (*env)->GetIntArrayRegion(env, javaArray, 0, 10, array);
  6. // do it
  7. // 提交修改
  8. (*env)->SetIntArrayRegion(env, javaArray, 0, 10, array);

当数组很大的时候,这个方法的效率就很低。

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

  1. jint *array;
  2. jboolean isCopy;
  3. jintArray javaArray;
  4. array = (*env)->GetIntArrayElements(env, javaArray, &isCopy);

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

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

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

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

三、NIO操作

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

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

  1. unsigned char *buff = (unsigned char *) malloc(1024);
  2. jobject directBuff;
  3. directBuff = (*env)->NewDirectByteBuffer(env, buffer, 1024);

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

2、获取Java字节缓冲区

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

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

四、访问域

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

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

  1. public class JavaClass {
  2. private String instanceField = "instance filed";
  3. private static String staticField = "static filed";
  4. private String getInstanceField() {
  5. return instanceField;
  6. }
  7. private static String getStaticField() {
  8. return staticField;
  9. }
  10. }

1、获取域ID

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

  1. jclass clazz;
  2. jobject instance;
  3. clazz = (*env)->GetObjectClass(env, instance);

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

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

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

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

2、获取域

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

  1. jstring field;
  2. // 获取实例域
  3. field = (*env)->GetObjectField(env, instance, fieldId);
  4. // 获取静态域
  5. field = (*env)->GetStaticObjectField(env, clazz, fieldId);

获取一个Java域的值就要调用两到三个JNI函数,非常麻烦,而且效率也比较低,建议把需要的参数传递给原生方法,这样可以提高性能。

四、调用方法

1、获取方法ID

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

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

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

2、调用方法

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

    1. jstring result;
    2. // 调用实例方法
    3. result = (*env)->CallStringMethod(env, instance, methodId);
    4. // 调用静态方法
    5. result = (*env)->CallStaticStringMethod(env, clazz, methodId);

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

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

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

  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开发篇:如何使用JNI中的global reference和local reference

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

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

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

  9. Android NDK开发 JNI操作java构造方法,普通方法,静态方法(七)

    Android NDK开发 JNI操作java普通.静态.构造方法 1.Jni实例化一个Java类的实例jobject 1.通过FindClas( ),获取Java类的的jclass 2.通过GetM ...

随机推荐

  1. Vue 中使用 TypeScript axios 使用方式

    Vue 中使用 TypeScript axios 使用方式 方式一 import axios from 'axios'; Vue.prototype.$axios = axios; // 在 .vue ...

  2. Linux下脚本文件第一行的作用

    Linux下脚本文件第一行的作用 在Linux/Unix系统中,你可以在脚本hello.py顶部添加以下命令让Python脚本可以像SHELL脚本一样可直接执行: #! /usr/bin/env py ...

  3. 【GS模型】全基因组选择之rrBLUP

    目录 1. 理论 2. 实操 2.1 rrBLUP包简介 2.2 实操 3. 补充说明 关于模型 关于交叉验证 参考资料 1. 理论 rrBLUP是基因组选择最常用的模型之一,也是间接法模型的代表.回 ...

  4. 【机器学习与R语言】8- 神经网络

    目录 1.理解神经网络 1)基本概念 2)激活函数 3)网络拓扑 4)训练算法 2.神经网络应用示例 1)收集数据 2)探索和准备数据 3)训练数据 4)评估模型 5)提高性能 1.理解神经网络 1) ...

  5. rabbitmq部署问题

    启动rabbitmq服务时报错: systemctl status rabbitmq-server 状态显示:Failed to start RabbitMQ broker Failed to sta ...

  6. SPI详解2

    串行外设接口 (SPI) 总线是一种运行于全双工模式下的同步串行数据链路.用于在单个主节点和一个或多个从节点之间交换数据. SPI 总线实施简单,仅使用四条数据信号线和控制信号线(请参见图 1). 图 ...

  7. college-ruled notebook

    TBBT.s3.e10: Sheldon: Where's your notebook?Penny: Um, I don't have one.Sheldon: How are you going t ...

  8. account, accomplish, accumulate

    account account从词源和count(数数)有关,和computer也有点关系.calculate则和'stone used in counting'有关.先看两个汉语的例子:1. 回头再 ...

  9. Can we access global variable if there is a local variable with same name?

    In C, we cannot access a global variable if we have a local variable with same name, but it is possi ...

  10. 通过js进行页面跳转的几种方式

    1.<a>标签 <a href="www.baidu.com" title="百度">百度</a> <a href= ...