美文网首页
Android NDK JNI 入门笔记-day03-引用数据类

Android NDK JNI 入门笔记-day03-引用数据类

作者: binglingziyu | 来源:发表于2020-03-20 23:46 被阅读0次

    * Android NDK JNI 入门笔记目录 *

    Java & JNI 引用数据类型

    对应于不同类型的 Java 对象, JNI 包含大量的引用类型

    java-jni-reference-types
    Java 的类类型 JNI 的引用类型 类型描述
    java.lang.Object jobject 可以表示任何 Java 的对象,或者没有 JNI 对应类型的 Java 对象(实例方法的强制参数)
    java.lang.String jstring Java 的 String 字符串类型的对象
    java.lang.Class jclass Java 的 Class 类型对象(静态方法的强制参数)
    Object[] jobjectArray Java 任何对象的数组表示形式
    boolean[] jbooleanArray Java 基本类型 boolean 的数组表示形式
    byte[] jbyteArray Java 基本类型 byte 的数组表示形式
    char[] jcharArray Java 基本类型 char 的数组表示形式
    short[] jshortArray Java 基本类型 short 的数组表示形式
    int[] jintArray Java 基本类型 int 的数组表示形式
    long[] jlongArray Java 基本类型 long 的数组表示形式
    float[] jfloatArray Java 基本类型 float 的数组表示形式
    double[] jdoubleArray Java 基本类型 double 的数组表示形式
    java.lang.Throwable jthrowable Java 的 Throwable 类型,表示异常的所有类型和子类

    JNI 引用类型-Java 基本数据类型数组

    写几个示例感受一下 JNI 对数组的操作。

    day03-array

    从 Native 获取数组,Java 进行格式化

    /**
     * 从 Native 获取数组,Java 进行格式化
     * @param view
     */
    public void getNativeArray(View view) {
        boolean[] nativeArray = NativeUtil.getNativeArray();
        StringBuilder stringBuilder = new StringBuilder("[");
        for(int i = 0; i < nativeArray.length; i++) {
            stringBuilder.append(nativeArray[i]);
            if(i != nativeArray.length -1) {
                stringBuilder.append(", ");
            }
        }
        stringBuilder.append("]");
        getNativeArrayText.setText(stringBuilder.toString());
    }
    
    // JNI 对数组的操作
    extern "C"
    JNIEXPORT jbooleanArray JNICALL
    Java_com_ihubin_ndkjni_NativeUtil_getNativeArray(JNIEnv *env, jclass clazz) {
        jboolean* jb = new jboolean[5];
        jb[0] = JNI_TRUE;
        jb[1] = JNI_FALSE;
        jb[2] = JNI_TRUE;
        jb[3] = JNI_FALSE;
        jb[4] = JNI_TRUE;
    
        jbooleanArray jba = env->NewBooleanArray(5);
        env->SetBooleanArrayRegion(jba, 0, 5, jb);
    
        return jba;
    }
    

    将 Java 数组传入 Native,Native 格式化

    /**
     * 将 Java 数组传入 Native,Native 格式化
     * @param view
     */
    public void formatArray(View view) {
        int[] intArray = {11, 22, 33, 44, 55};
        String formatArrayStr = NativeUtil.formatArray(intArray);
        formatArrayText.setText(formatArrayStr);
    }
    
    // JNI 对数组的操作
    Java_com_ihubin_ndkjni_NativeUtil_formatArray(JNIEnv *env, jclass clazz, jintArray int_array) {
        jint array[5];
        env->GetIntArrayRegion(int_array, 0, 5, array);
        jsize size = env->GetArrayLength(int_array);
        char resutStr[100] = {0};
        char str[10] = {0};
        strcat(resutStr, "[");
        for(int i = 0; i < size; i++) {
            sprintf(str, "%d", array[i]);
            strcat(resutStr, str);
            if(i != size - 1) {
                strcat(resutStr, ", ");
            }
        }
        strcat(resutStr, "]");
        return env->NewStringUTF(resutStr);
    }
    

    Native 计算商品总价

    /**
     * Native 计算商品总价
     * @param view
     */
    public void calcTotalMoney(View view) {
        double[] price = {5.5, 6.6, 7.7, 8.8, 9.9};
        String resultStr = NativeUtil.calcTotalMoney(price);
        calcTotalMoneyText.setText(resultStr);
    }
    
    // JNI 对数组的操作
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_ihubin_ndkjni_NativeUtil_calcTotalMoney(JNIEnv *env, jclass clazz, jdoubleArray price) {
        jdouble array[5];
        env->GetDoubleArrayRegion(price, 0, 5, array);
        jsize size = env->GetArrayLength(price);
    
        char resutStr[255] = {0};
        char str[20] = {0};
        strcat(resutStr, "sum(");
        jdouble totalMoney = 0.0;
        for(int i = 0; i < size; i++) {
            sprintf(str, "%.1f", array[i]);
            strcat(resutStr, str);
            if(i != size - 1) {
                strcat(resutStr, ", ");
            }
            totalMoney += array[i];
        }
        strcat(resutStr, ")");
        strcat(resutStr, "\n=");
        sprintf(str, "%.1f", totalMoney);
        strcat(resutStr, str);
        return env->NewStringUTF(resutStr);
    }
    

    Native 计算各科成绩是否通过

    /**
     * 计算各科成绩是否通过
     * @param view
     */
    public void calcScorePass(View view) {
        float[] yourScore = {59.0F, 88.0F, 76.5F, 45.0F, 98.0F};
        String[] yourScoreResult = NativeUtil.calcScorePass(yourScore);
        StringBuilder stringBuilder = new StringBuilder();
    
        stringBuilder.append("[");
        for(int i = 0; i < yourScore.length; i++) {
            stringBuilder.append(yourScore[i]);
            if(i != yourScore.length - 1) {
                stringBuilder.append(", ");
            }
        }
        stringBuilder.append("]");
    
        stringBuilder.append("\n");
    
        stringBuilder.append("[");
        for(int i = 0; i < yourScoreResult.length; i++) {
            stringBuilder.append(yourScoreResult[i]);
            if(i != yourScoreResult.length - 1) {
                stringBuilder.append(", ");
            }
        }
        stringBuilder.append("]");
    
        calcScorePassText.setText(stringBuilder.toString());
    }
    
    
    extern "C"
    JNIEXPORT jobjectArray JNICALL
    Java_com_ihubin_ndkjni_NativeUtil_calcScorePass(JNIEnv *env, jclass clazz, jfloatArray your_score) {
        jfloat array[5];
        env->GetFloatArrayRegion(your_score, 0, 5, array);
        jsize size = env->GetArrayLength(your_score);
    
        jclass objClass = env->FindClass("java/lang/String");
        jobjectArray objArray = env->NewObjectArray(5, objClass, 0);
        jstring  jstr;
        for(int i = 0; i < size; i++) {
            if(array[i] >= 60.0) {
                jstr = env->NewStringUTF("√");
            } else {
                jstr = env->NewStringUTF("×");
            }
            env->SetObjectArrayElement(objArray, i, jstr);
        }
    
        return objArray;
    }
    

    查看一下结果:

    day03-array-result

    到这里,我们已经‘熟练掌握’了 JNI 对 Java 基本数据类型数组的各种操作。

    Native 计算各科成绩是否通过 这个示例中,出现了一段代码 jclass objClass = env->FindClass("java/lang/String"); 下面就来探索一下。

    JNI 引用类型-Java 对象

    之前,都是在 Java 调用 Native 中的方法,现在,Native 代码反调用 Java 层代码。

    Class/属性和方法/对象

    一、获取 Class 对象

    为了能够在 C/C++ 中调用 Java 中的类,jni.h 的头文件专门定义了 jclass 类型表示 Java 中 Class 类。JNIEnv 中有 3 个函数可以获取 jclass。

    // 通过类的名称(类的全名,这时候包名不是用'"."点号而是用"/"来区分的)来获取 jclass
    jclass FindClass(const char* clsName)
    
    // 通过对象实例来获取 jclass,相当于 Java 中的 getClass() 函数
    jclass GetObjectClass(jobject obj)
    
    // 通过 jclass 可以获取其父类的 jclass 对象
    jclass getSuperClass(jclass obj)
    

    例:

    // 获取 Java 中的类 java.lang.String
    jclass objClass = env->FindClass("java/lang/String");
    
    // 获取一个类的父类
    jclass superClass = env->getSuperClass(objClass);
    

    二、获取属性方法

    在 C/C++ 获取 Java 层的属性和方法,JNI 在 jni.h 头文件中定义了 jfieldID 和 jmethodID 这两种类型来分别代表 Java 端的属性和方法。

    // 获取属性
    jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
    
    // 获取方法
    jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
    
    // 获取静态属性
    jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
    
    // 获取静态方法
    jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig)
    

    例:

    package com.ihubin.ndkjni;
    
    public class User {
    
        public static int staticField = 88;
    
        public int normalField = 99;
    
        public static String getStaticUserInfo() {
            return "[name:hubin, age:18]";
        }
        
        public String getNormalUserInfo() {
            return "[name:hubin, age:28]";
        }    
        
         private String name;
    
        private int age;
    
        public User() {}
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getFormatInfo() {
            return String.format("[name:%s, age:%d]", name, age);
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
        
    }
    
    
    // 获取 jclass
    jclass userClass = env->FindClass("com/ihubin/ndkjni/User");
    
    // 获取属性 ID
    jfieldID normalField = env->GetFieldID(userClass, "normalField", "I");
    // 获取静态属性 ID
    jfieldID staticField = env->GetStaticFieldID(userClass, "staticField", "I");
    
    // 获取方法 ID
    jmethodID normalMethod = env->GetMethodID(userClass, "getNormalUserInfo", "()Ljava/lang/String;");
    // 获取静态方法 ID
    jmethodID staticMethod = env->GetStaticMethodID(userClass, "getStaticUserInfo", "()Ljava/lang/String;");
    
    // 获取无参构造函数
    jmethodID voidInitMethod = env->GetMethodID(userClass, "<init>", "()V");
    // 获取有参构造函数
    jmethodID paramInitMethod = env->GetMethodID(userClass, "<init>", "(Ljava/lang/String;I)V");
    

    三、构造对象

    类实例化以后才能访问里面的非静态属性、方法,下面通过上面获取到的 jclass 和构造函数 jmethodID 构造对象。

    // 通过 clazz/methodID/...(可变参数列表) 创建一个对象
    jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
    
    // args(参数数组)
    jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
    
    // args(指向变参列表的指针)
    jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
    
    // 通过一个类创建一个对象,默认构造函数
    jobject AllocObject(JNIEnv *env, jclass clazz)
    

    例:

    // 通过无参构造函数
    jobject userOne = env->NewObject(userClass, voidInitMethod);
    
    // 通过有参构造函数
    jstring name = env->NewStringUTF("HUBIN");
    jint age = 8;
    jobject userTwo = env->NewObject(userClass, paramInitMethod, name, age);
    
    // 默认构造函数
    jobject userThree = env->AllocObject(userClass);
    

    四、获取属性、调用方法

    之前的准备都是为了能使用 Java 对象中的属性、方法。

    获取、设置属性值:
    XXX Get<type>Field(jobject obj, jfieldID fieldID)
    Set<type>Field(jobject obj, jfieldID fieldID, XXX value)

    获取、设置静态属性值:
    XXX GetStatic<type>Field(jclass clazz, jfieldID fieldID)
    SetStatic<type>Field(jclass clazz, jfieldID fieldID, XXX value)

    调用方法:
    NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...)
    NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
    NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)

    调用静态方法:
    NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...)
    NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args)
    NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)

    例:

    // 获取对象中的属性
    jint normalFieldValue = env->GetIntField(userOne, normalField);
    LOGD("normalField: %d", normalFieldValue);
    
    // 获取 class 中静态属性
    jint staticFieldValue = env->GetStaticIntField(userClass, staticField);
    LOGD("staticField: %d", staticFieldValue);
    
    // 调用对象中的方法
    jobject normalMethodResultObj = env->CallObjectMethod(userOne, normalMethod);
    jstring normalMethodResult = static_cast<jstring>(normalMethodResultObj);
    const char *normalMethodResultNativeString = env->GetStringUTFChars(normalMethodResult, 0);
    LOGD("normalMethodResult: %s", normalMethodResultNativeString);
    
    // 调用 class 中的静态方法
    jobject staticMethodResultObj = env->CallStaticObjectMethod(userClass, staticMethod);
    jstring staticMethodResult = static_cast<jstring>(staticMethodResultObj);
    const char *staticMethodResultNativeString = env->GetStringUTFChars(staticMethodResult, 0);
    LOGD("staticMethodResult: %s", staticMethodResultNativeString);
    
    // 调用对象中的方法
    jobject getFormatInfoMethodResultObj = env->CallObjectMethod(userTwo, getFormatInfoMethod);
    jstring getFormatInfoMethodResult = static_cast<jstring>(getFormatInfoMethodResultObj);
    const char *getFormatInfoMethodResultNativeString = env->GetStringUTFChars(getFormatInfoMethodResult, 0);
    LOGD("getFormatInfoMethodResult: %s", getFormatInfoMethodResultNativeString);
    
    // 测试 jobject AllocObject(JNIEnv *env, jclass clazz) 创建的对象
    jobject userThreeMethodResultObj = env->CallObjectMethod(userThree, normalMethod);
    jstring userThreeMethodResult = static_cast<jstring>(userThreeMethodResultObj);
    const char *userThreeMethodResultNativeString = env->GetStringUTFChars(userThreeMethodResult, 0);
    LOGD("userThreeMethodResult: %s", userThreeMethodResultNativeString);
    
    day03-example-result

    在获取 jfieldID 、 jmethodID 时,出现了一些奇怪的字符串 I ()Ljava/lang/String; ()V (Ljava/lang/String;I)V,下面就来研究一下。

    数据类型签名

    在 JVM 虚拟机中,存储数据类型的名称时,是使用指定的类型签名来存储,而不是我们习惯的 int,float 等。
    JNI 使用的就是这种类型签名。

    Java 类型 类型签名
    int I
    long J
    byte B
    short S
    char C
    float F
    double D
    boolean Z
    void V
    其他引用类型 L+类全名+;
    数组 [
    方法 (参数)返回值

    例子1

    Java 类型:java.lang.String
    类型签名:Ljava/lang/String;
    即一个 Java 类对应的签名,就是 L 加上类的全名,其中 . 要换成 / ,最后不要忘掉末尾的分号。
    

    例子2

    Java 类型:String[]
    类型签名:[Ljava/lang/String;
    
    Java 类型:int[][]
    类型签名:[[I
    
    数组就是简单的在类型描述符前加 [ 即可,二维数组就是两个 [ ,以此类推。
    

    例子3

    Java 方法:long f (int n, String s, int[] arr);
    类型签名:(ILjava/lang/String;[I)J
    
    Java 方法:void f ();
    类型签名:()V
    
    括号内是每个参数的类型符,括号外就是返回值的类型符。
    

    使用 javap -s <Class> 查看签名

    javap-get-method-signature

    至此,我们已经学会了在 Android 项目中 Native 操作 Java 对象。


    代码:

    NDKJNIday03

    参考资料:

    Oracle - JNI Types and Data Structures

    JNI基础:JNI数据类型和类型描述符

    Android JNI学习(三)——Java与Native相互调用

    JNI完全指南(四)——对象操作


    相关文章

      网友评论

          本文标题:Android NDK JNI 入门笔记-day03-引用数据类

          本文链接:https://www.haomeiwen.com/subject/rtdeyhtx.html