虽然说使用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. Atcoder Grand Contest 020 E - Encoding Subsets(记忆化搜索+复杂度分析)

    Atcoder 题面传送门 & 洛谷题面传送门 首先先考虑如果没有什么子集的限制怎样计算方案数.明显就是一个区间 \(dp\),这个恰好一年前就做过类似的题目了.我们设 \(f_{l,r}\) ...

  2. Python基础之流程控制if判断

    目录 1. 语法 1.1 if语句 1.2 if...else 1.3 if...elif...else 2. if的嵌套 3. if...else语句的练习 1. 语法 1.1 if语句 最简单的i ...

  3. 前后端分离进阶一:使用ElementUI+前端分页

    前两篇入门:前后端分离初体验一:前端环境搭建 前后端分离初体验二:后端环境搭建+数据交互 参考:https://www.bilibili.com/video/BV137411B7vB B站UP:楠哥教 ...

  4. academy

    academy at/in school都行,academy一般用at. The word comes from the Academy in ancient Greece, which derive ...

  5. Node.js 概述

    JavaScript 标准参考教程(alpha) 草稿二:Node.js Node.js 概述 GitHub TOP Node.js 概述 来自<JavaScript 标准参考教程(alpha) ...

  6. Android WifiP2p实现

    Android WifiP2p实现 Wifi Direct功能早在Android 4.0就以经加入Android系统了,但是一直没有很好的被支持,主要原因是比较耗电而且连接并不是很稳定.但是也有很大的 ...

  7. maven内存溢出解决方法

    maven内存溢出(InvocationTargetException: PermGen space) 解决方案:maven脚本:mvn.bat文件@REM set MAVEN_OPTS=-Xdebu ...

  8. entfrm-boot开发平台一览【entfrm开源模块化无代码开发平台】

    介绍 entfrm-boot是一个以模块化为核心的无代码开发平台,能够让中小企业快速从零搭建自己的开发平台:开箱即用,可插拔可自由组合:以模块化的方式,最大化的代码复用,避免重复开发:无代码可视化开发 ...

  9. 【C/C++】最大连续子序列和/动态规划

    思路主要是看了晴神的算法笔记,实现是自己重新用vector实现了一下,对付逗号隔开的输入 #include <iostream> #include <string> #incl ...

  10. 记一次 .NET 某妇产医院 WPF内存溢出分析

    一:背景 1. 讲故事 上个月有位朋友通过博客园的短消息找到我,说他的程序存在内存溢出情况,寻求如何解决. 要解决还得通过 windbg 分析啦. 二:Windbg 分析 1. 为什么会内存溢出 大家 ...