虽然说使用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 008 E - Next or Nextnext(乱搞+找性质)

    Atcoder 题面传送门 & 洛谷题面传送门 震惊,我竟然能独立切掉 AGC E 难度的思维题! hb:nb tea 一道 感觉此题就是找性质,找性质,再找性质( 首先看到排列有关的问题,我 ...

  2. nginx_日志

    192.168.31.250 - - [13/Nov/2019:08:38:07 +0800] "GET /aa HTTP/1.1" 404 571 "-" & ...

  3. LearnPython_week4

    1.装饰器2.生成器3.迭代器4.内置方法5.可序列化6.项目规范化 1.装饰器 # -*- coding:utf-8 -*- # Author:Wong Du ### 原代码 def home(): ...

  4. C++你不知道的事

    class A { public: A() { cout<<"A's constructor"<<endl; } virtual ~A() { cout&l ...

  5. .Net Core——用SignalR撸个游戏

    之前开内部培训,说到实时web应用这一块讲到了SignalR,我说找时间用它做个游戏玩玩,后面时间紧张就一直没安排.这两天闲了又想起这个事,考虑后决定用2天时间写个斗D主,安排了前端同学写客户端,我写 ...

  6. 【模板】网络最大流(EK、Dinic、ISAP)(网络流)/洛谷P3376

    题目链接 https://www.luogu.com.cn/problem/P3376 题目大意 输入格式 第一行包含四个正整数 \(n,m,s,t\),分别表示点的个数.有向边的个数.源点序号.汇点 ...

  7. 表格合并单元格【c#】

    gridBranchInfo.DataSource = dtBranchViewList; gridBranchInfo.DataBind(); Random random = new Random( ...

  8. Freeswitch 安装爬坑记录1

    2 Freeswitch的安装 2.1 准备工作 服务器安装CentOS 因为是内部环境,可以关闭一些防火墙设置,保证不会因为网络限制而不能连接 关闭防火墙 查看防火墙 systemctl statu ...

  9. 学习java的第二十三天

    一.今日收获 1.java完全学习手册第三章算法的3.2排序,比较了跟c语言排序上的不同 2.观看哔哩哔哩上的教学视频 二.今日问题 1.快速排序法的运行调试多次 2.哔哩哔哩教学视频的一些术语不太理 ...

  10. day10 ajax的基本使用

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