SetBooleanArrayRegion(jba, 0, 5, jb);return jba;} 将 Java 数组传入 Native,Native 格式化 /** * 将。android|Android NDK JNI 入门笔记-day03-引用数据类型。" />

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

Android NDK JNI 入门笔记目录
Java & JNI 引用数据类型

对应于不同类型的 Java 对象, JNI 包含大量的引用类型
android|Android NDK JNI 入门笔记-day03-引用数据类型
文章图片

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 对数组的操作。
android|Android NDK JNI 入门笔记-day03-引用数据类型
文章图片

从 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); jstringjstr; 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; }

查看一下结果:
android|Android NDK JNI 入门笔记-day03-引用数据类型
文章图片

到这里,我们已经‘熟练掌握’了 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, "", "()V"); // 获取有参构造函数 jmethodID paramInitMethod = env->GetMethodID(userClass, "", "(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 GetField(jobject obj, jfieldID fieldID)
SetField(jobject obj, jfieldID fieldID, XXX value)
获取、设置静态属性值:
XXX GetStaticField(jclass clazz, jfieldID fieldID)
SetStaticField(jclass clazz, jfieldID fieldID, XXX value)
调用方法:
NativeType CallMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...)
NativeType CallMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
NativeType CallMethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
调用静态方法:
NativeType CallStaticMethod(JNIEnv *env, jclass clazz, jmethodID methodID, ...)
NativeType CallStaticMethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args)
NativeType CallStaticMethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)
例:
// 获取对象中的属性 jint normalFieldValue = https://www.it610.com/article/env->GetIntField(userOne, normalField); LOGD("normalField: %d", normalFieldValue); // 获取 class 中静态属性 jint staticFieldValue = https://www.it610.com/article/env->GetStaticIntField(userClass, staticField); LOGD("staticField: %d", staticFieldValue); // 调用对象中的方法 jobject normalMethodResultObj = env->CallObjectMethod(userOne, normalMethod); jstring normalMethodResult = static_cast(normalMethodResultObj); const char *normalMethodResultNativeString = env->GetStringUTFChars(normalMethodResult, 0); LOGD("normalMethodResult: %s", normalMethodResultNativeString); // 调用 class 中的静态方法 jobject staticMethodResultObj = env->CallStaticObjectMethod(userClass, staticMethod); jstring staticMethodResult = static_cast(staticMethodResultObj); const char *staticMethodResultNativeString = env->GetStringUTFChars(staticMethodResult, 0); LOGD("staticMethodResult: %s", staticMethodResultNativeString); // 调用对象中的方法 jobject getFormatInfoMethodResultObj = env->CallObjectMethod(userTwo, getFormatInfoMethod); jstring getFormatInfoMethodResult = static_cast(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(userThreeMethodResultObj); const char *userThreeMethodResultNativeString = env->GetStringUTFChars(userThreeMethodResult, 0); LOGD("userThreeMethodResult: %s", userThreeMethodResultNativeString);

android|Android NDK JNI 入门笔记-day03-引用数据类型
文章图片

在获取 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)JJava 方法:void f (); 类型签名:()V括号内是每个参数的类型符,括号外就是返回值的类型符。

使用 javap -s 查看签名
android|Android NDK JNI 入门笔记-day03-引用数据类型
文章图片

至此,我们已经学会了在 Android 项目中 Native 操作 Java 对象。
代码:
NDKJNIday03
参考资料:
【android|Android NDK JNI 入门笔记-day03-引用数据类型】Oracle - JNI Types and Data Structures
JNI基础:JNI数据类型和类型描述符
Android JNI学习(三)——Java与Native相互调用
JNI完全指南(四)——对象操作

    推荐阅读