zoukankan      html  css  js  c++  java
  • 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,崩溃的时候假设没有抛出异常,那么原生代码就会停止执行,应用程序会发生闪退。

  • 相关阅读:
    Luogu3227 HNOI2013切糕
    Luogu1646 happiness
    Luogu5038 SCOI2012奇怪的游戏
    Luogu3324 星际战争
    Luogu2472 SCOI2007蜥蜴
    NOI Online#3 解题报告
    Luogu6478 游戏
    1,[VS入门教程] 使用Visual Studio写c语言 入门与技巧精品文~~~~下载安装篇
    Windows开机自动登陆 开/关:登录需按Ctrl+Alt+del的功能
    官方入门教程和文档 | Visual Studio
  • 原文地址:https://www.cnblogs.com/blfshiye/p/3764377.html
Copyright © 2011-2022 走看看