虽然说使用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. Navicat 激活教程2021(Linux)

    Navicat 激活教程2021(Linux) 目录 背景 环境 激活 清理 使用 背景 Navicat 是香港卓软数字科技有限公司生产的一系列 MySQL.MariaDB.MongoDB.Oracl ...

  2. Redis队列跟MQ的区别

    Redis队列:Redis队列是一个Key-Value的NoSQL数据库,开发维护很活跃,虽然是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用 ...

  3. session与cookie 浏览器关闭时的区别

    session与cookie 浏览器关闭时的区别 cookie是存储在本地,当cookie在浏览器关闭的时候,再次打开是否记录之前的值,这跟cookie的过期时间设置有关. 如果cookie的过期时间 ...

  4. jsp的动态包含和静态包含

    jsp的动态包含和静态包含 例如:提取一个公共的页面(top.jsp)到/WEB-INF/jsp/common/目录下 动态包含: 被包含的页面也会独立编译,生成字节码文件,一般包含页面信息频繁变化的 ...

  5. JDBC01 获取数据库连接

    概述 Java Database Connectivity(JDBC)直接访问数据库,通用的SQL数据库存取和操作的公共接口,定义访问数据库的标准java类库(java.sql,javax.sql) ...

  6. day10 ajax的基本使用

    day10 ajax的基本使用 今日内容 字段参数之choices(重要) 多对多的三种创建方式 MTV与MVC理论 ajax语法结构(固定的) 请求参数contentType ajax如何传文件及j ...

  7. Mapreduce中的join操作

    一.背景 MapReduce提供了表连接操作其中包括Map端join.Reduce端join还有半连接,现在我们要讨论的是Map端join,Map端join是指数据到达map处理函数之前进行合并的,效 ...

  8. Sharding-JDBC 实现水平分表

    1.搭建环 (1) 技术: SpringBoot2.2.1+ MyBatisPlus + Sharding-JDBC + Druid 连接池(2)创建 SpringBoot 工程

  9. 【leetcode】153. Find Minimum in Rotated Sorted Array

    Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example ...

  10. 查看linux系统CPU和内存命令

    cat /proc/cpuinfo查看linux系统的CPU型号.类型以及大小,如下图所示.   通过greap命令根据Physical Processor ID筛选出多核CPU的信息.   cat ...