上篇文章JNI基本类型数组操作讲述了如何在JNI层操作基本类型的数组,本片文章承继上篇文章来讲解如何在JNI层操作引用类型的数组。不过在阅读本篇文章前,你可能需要先理解 JNI访问Java对象的成员 和 JNI访问Java类的静态成员 这两篇文章所讲,否则你看本文会有点吃力。
在讲解之前呢,需要做一些准备工作。
首先需要一个Java引用类型的类 Person.java
,这个类将被用作创建数组
package com.uni.ndkdemo; public class Person { private String mName; public Person(String name) { mName = name; } public void sayHello() { System.out.println("Hello, " + mName + "!"); } } 复制代码
然后得准备一个 native
入口的类
package com.uni.ndkdemo; public class ArrayTest { static { System.loadLibrary("array_jni"); } public native void sayHello(Person[] persons); public static void main(String[] args) { ArrayTest arrayTest = new ArrayTest(); Person persons[] = new Person[2]; persons[0] = new Person("Jay Chow"); persons[1] = new Person("Stephen Chow"); arrayTest.sayHello(persons); } } 复制代码
ArrayTest.java
的 main()
方法中创建一个了 Person
数组,并给每个元素赋值,然后调用了 sayHello()
这个 native
方法把这 Person
数组传入JNI层。
最后,需要在JNI层实现对应的函数,假设这个函数如下
static void com_uni_ndkdemo_ArrayTest_sayHello(JNIEnv *env, jobject thiz, jobjectArray objectArray) { } 复制代码
我将在这个函数中来讲解JNI如何操作引用类型数组。
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); 复制代码
GetObjectArrayElement
函数获取数组 array
在索引 index
下的元素。
注意,这个函数只能用于获取引用类型数组的元素,对于如何获取基本类型数组的元素,请参考JNI基本类型数组操作。
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz); 复制代码
IsInstanceOf
函数用于判断对象 obj
是否是 clazz
类的实例对象。
可以利用这个函数来判断数组元素的类型,这样就可以避免调用了错的方法。
static void com_uni_ndkdemo_ArrayTest_sayHello(JNIEnv *env, jobject thiz, jobjectArray objectArray) { // 1. 获取数组的长度 jsize length = env->GetArrayLength(objectArray); // 2. 获取Person类的Class对象 jclass clazz_Person = env->FindClass("com/uni/ndkdemo/Person"); if (clazz_Person == NULL) { return; } // 3. 获取Person的sayHello方法字段 jmethodID methodID_sayHello = env->GetMethodID(clazz_Person, "sayHello", "()V"); if (methodID_sayHello == NULL) { return; } // 4. 循环调用每个Person对象的sayHello()方法 for (int i = 0; i < length; i++) { // 获取引用类型数组的对象 jobject element = env->GetObjectArrayElement(objectArray, i); // 判断数组元素是否是Person类对象 if (env->IsInstanceOf(element, clazz_Person)) { // 调用Person对象的sayHello()方法 env->CallVoidMethod(element, methodID_sayHello); } } } 复制代码
首先通过第二步和第三步来获取 Person
类的 sayHello()
方法的 jmethodID
,然后调用 GetObjectArrayElement
来获取数组的元素,最后通过 IsInstanceOf
判断数组元素是 Person
类对象后,就调用了它的 sayHello
方法。
前面所讲的都是处理从Java层传入JNI层的引用类型数组,当然,也可以在JNI层创建引用类型数组,并返回给Java层,这就要用到 NewObjectArray
函数
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); 复制代码
NewObjectArray
会根据参数 elementClass
的类型,创建一个长度为 length
的数组。
如果你指定了第四个参数 initialElement
,那么将会用第四个参数初始化数组的所有元素。如果指定 initialElement
为 NULL
,那么数组所有元素为 NULL
。
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); 复制代码
SetObjectArrayElement
函数是为数组 array
,设置索引 index
下的元素的值 value
。
NewObjectArray
可以在创建数组的时候,用参数 jobject initialElement
给数组每个元素赋初值。 SetObjectArrayElement
函数可以用参数 jobject value
给数组元素设置值。那么问题来了,这个用于赋值的对象如何创建呢?可以使用 NewObject
或 NewObjectA
或 NewObjectV
。
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...); jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); 复制代码
这三个函数的区别在于传入参数的形式不一样而已。
参数 clazz
代表Java类的 Class
对象,可以通过 FindClass
函数来获取。
参数 methodID
代表Java类的构造函数,需要通过 GetMethodID
来获取,不过调用 GetMethodID
函数时,传入的函数名参数一定要为 <init>
,而且传入函数签名参数的返回值一定要为 V
。这个可能说的有点抽象,不过可以从后面的例子中看出如何使用。
现在需要先准备一个Java的 native
方法,返回一个 Person
数组public class ArrayTest {
static {
System.loadLibrary("array_jni");
}
public native long native_sum(int[] a); public native int[] getNativeArray(); public native void sayHello(Person[] persons); public native void handleArray(int[] a); public native void nativeSayHello(int length); public native Person[] getNativePersons(int length); 复制代码
}
public class ArrayTest { static { System.loadLibrary("array_jni"); } public native Person[] getNativePersons(int length); public static void main(String[] args) { for (int i = 0; i < nativePersons.length; i++) { nativePersons[i].sayHello(); } } } 复制代码
然后到JNI层去实现
static const int NAME_SIZE = 2; static const char *names[NAME_SIZE] = {"周星驰", "周杰伦"}; static jobjectArray getPersons(JNIEnv *env, jobject thiz, jint length) { // 1. 找到Person类的Class对象 jclass clazz_Person = env->FindClass("com/uni/ndkdemo/Person"); if (clazz_Person == NULL) { return NULL; } // 2. 找到Person类的构造函数的jmethodID // 注意,这里找的是带有String参数的构造函数 jmethodID methodId_Person_constructor = env->GetMethodID(clazz_Person, "<init>", "(Ljava/lang/String;)V"); if (methodId_Person_constructor == NULL) { return NULL; } // 3. 创建一个Person数组,每个元素为NULL jobjectArray array = env->NewObjectArray(length, clazz_Person, NULL); for (int i = 0; i < length; i++) { const char * name = names[i % NAME_SIZE]; // 4. 创建Person对象 jobject person = env->NewObject(clazz_Person, methodId_Person_constructor, env->NewStringUTF(name)); // 5. 给数组元素设置值 env->SetObjectArrayElement(array, i, person); } return array; } 复制代码
第一步,要找到 Person
类,因为要利用它来创建 Person
数组。
第二步,要找到 Person
类的 Person(String name)
构造函数。注意,调用 GetMethodID
是,构造函数名参数为 <init>
,构造函数签名参数为 (Ljava/lang/String;)V
。很明显获取的是带 String
参数的构造函数。
第三步,创建 Person
数组,由于调用 NewObjectArray
函数时第三个参数为 NULL
,那么数组中每个元素的值为 NULL
。
第四步,调用 NewObject
函数,使用 Person(String name)
构造函数来创建一个对象。
第五步,调用 SetObjectArrayElement
函数来给数组的元素赋值。
这五步看似简单,但是需要有相应的JNI功底,如果你看完了我前面所有文章,那么这里就非常简单了。
JNI处理引用类型的数组要比处理基本类型数组要复杂得多,因为要涉及到很多的方方面面,但是这些方方面面都在前面的文章中讲过。所有,如果你看这篇文章有困惑,不妨去前面的文章中寻找答案。
Java的 String
类也是一个引用类型,但是这个引用类型是用数组实现的,因此比较特殊。在下一篇文章中,我将会来讲解JNI如何来操作字符串的。