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

相關文章
相關標籤/搜索