转载

JNI引用类型数组操作

上篇文章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.javamain() 方法中创建一个了 Person 数组,并给每个元素赋值,然后调用了 sayHello() 这个 native 方法把这 Person 数组传入JNI层。

最后,需要在JNI层实现对应的函数,假设这个函数如下

static void com_uni_ndkdemo_ArrayTest_sayHello(JNIEnv *env, jobject thiz, jobjectArray objectArray)
{
 
}
复制代码

我将在这个函数中来讲解JNI如何操作引用类型数组。

GetObjectArrayElement

jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
复制代码

GetObjectArrayElement 函数获取数组 array 在索引 index 下的元素。

注意,这个函数只能用于获取引用类型数组的元素,对于如何获取基本类型数组的元素,请参考JNI基本类型数组操作。

IsInstanceOf

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 方法。

NewObjectArray

前面所讲的都是处理从Java层传入JNI层的引用类型数组,当然,也可以在JNI层创建引用类型数组,并返回给Java层,这就要用到 NewObjectArray 函数

jobjectArray NewObjectArray(JNIEnv *env, jsize length,
                        jclass elementClass, jobject initialElement);
复制代码

NewObjectArray 会根据参数 elementClass 的类型,创建一个长度为 length 的数组。

如果你指定了第四个参数 initialElement ,那么将会用第四个参数初始化数组的所有元素。如果指定 initialElementNULL ,那么数组所有元素为 NULL

SetObjectArrayElement

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, 
                        jsize index, jobject value);
复制代码

SetObjectArrayElement 函数是为数组 array ,设置索引 index 下的元素的值 value

NewObjectArray 可以在创建数组的时候,用参数 jobject initialElement 给数组每个元素赋初值。 SetObjectArrayElement 函数可以用参数 jobject value 给数组元素设置值。那么问题来了,这个用于赋值的对象如何创建呢?可以使用 NewObjectNewObjectANewObjectV

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如何来操作字符串的。

原文  https://juejin.im/post/5d2ed0ac6fb9a07f0655a267
正文到此结束
Loading...