JNI引用类型数组操做

上篇文章JNI基本类型数组操做讲述了如何在JNI层操做基本类型的数组,本片文章承继上篇文章来说解如何在JNI层操做引用类型的数组。java

若是在阅读本文的时候,你发现有不懂的操做,而我又没有详细解释的,能够从下面几篇文章中寻找答案c++

准备工做

在讲解以前呢,须要作一些准备工做。数组

首先须要一个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入口的类post

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层。ui

最后,经过函数动态注册,须要在JNI层实现对应的函数,假设这个函数以下spa

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

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

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给数组元素设置值。那么问题来了,这个用于赋值的对象如何建立呢?可使用 NewObject/NewObjectA/NewObjectV,或者 AllocObject函数。

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。这个可能说的有点抽象,不过能够从后面的例子中看出如何使用。

AllocObject

jobject AllocObject(JNIEnv *env, jclass clazz);
复制代码

AllocObject函数很是有意思,它会为clazz类的对象分配内存,而不用调用clazz所指定类的任何构造函数,并返回一个指向这个对象的引用。

AllocObject函数只是为对象分配内存,可是并无给对象的变量赋值,所以须要咱们手动对给对象的变量进行赋值。而前面讲的NewObject函数须要指定调用哪一个构造函数,而且若是构造函数带有参数,还得在调用的时候传入相应的参数。

例子

如今须要先准备一个Java的native方法,返回一个Person数组

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/umx/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);

    // 4. 循环为数组中的每一个元素赋值
    for (int i = 0; i < length; i++) {
        const char *name = names[i % NAME_SIZE];
        jobject person = NULL;
        if (i == length - 1) {
            // 为Person对象分配内存
            person = env->AllocObject(clazz_Person);
            jmethodID methodID_setName = env->GetMethodID(clazz_Person, "setName",
                                                          "(Ljava/lang/String;)V");
            // 调用setName()方法给建立的Person对象的mName变量赋值
            env->CallVoidMethod(person, methodID_setName, env->NewStringUTF(name));
        } else {
            // 调用Person(String name)构造函数建立Person对象
            person = env->NewObject(clazz_Person, methodId_Person_constructor,
                                    env->NewStringUTF(name));
        }
        // 给数组元素设置值
        env->SetObjectArrayElement(array, i, person);

        // 释放局部变量
        env->DeleteLocalRef(person);
    }
    return array;
}
复制代码

第三步调用NewObjectArray来建立Person数组,可是因为最后一个参数为NULL,所以数组元素的值都为NULL。那么就须要第四步来为数组每一个元素赋值。

在第四步的实现中,若是是数组中最后一个元素,那么就调用AllocObject函数来为Person对象分配内存,可是这个对象尚未数据,必须经过调用setName方法来设置mName的值。而数组中其它元素都是调用Person(String name)这个构造函数来建立对象的,因此在调用NewObject的时候得传入一个参数。

对象建立好了以后就须要为调用SetObjectArrayElement来为每一个元素赋值。

若是这个实现中还有不懂的地方,请参考文章开头前指定的文章。

结束

JNI处理引用类型的数组要比处理基本类型数组要复杂得多,由于要涉及到不少的方方面面,可是这些方方面面都在前面的文章中讲过。全部,若是你看这篇文章有困惑,不妨去前面的文章中寻找答案。

Java的String类也是一个引用类型,可是这个引用类型是用数组实现的,所以比较特殊。在下一篇文章中,我将会来说解JNI如何来操做字符串的。

相关文章
相关标签/搜索