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

* 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完全指南(四)——对象操作


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容