Android 經過 JNI 調用 Java 類的構造方法和父類的方法

Android 還能夠經過 JNI 來調用 Java 一個類的構造方法,從而建立一個 Java 類。java

調用構造方法

調用構造方法的步驟和以前調用類的實例方法步驟相似,也須要得到對應的類和方法 id。git

對於類,經過 FindClass 能夠找到對應的 Java 類型。github

對於構造方法,它的方法 id 仍是經過 GetMethodID 方法來得到,可是構造方法對應的名稱爲 <init>,返回值類型是 void 類型的。數組

完成了以上準備條件後,就能夠經過 NewObject 來調用構造方法,從而建立具體的類。微信

下面以 String 的某個構造方法爲例函數

public String(char value[]) // Java String 類的其中一個構造方法

對應的 C++ 代碼:this

extern "C"
JNIEXPORT jstring JNICALL
Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_invokeStringConstructors(JNIEnv *env, jobject instance) {

    jclass stringClass;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    // 由 C++ 字符串建立一個 Java 字符串
    jstring temp = env->NewStringUTF("this is char array");
    // 再從 Java 字符串得到一個字符數組指針,做爲 String 構造函數的參數
    const jchar *chars = env->GetStringChars(temp, NULL);
    int len = 10;

    stringClass = env->FindClass("java/lang/String"); // 找到具體的 String 類
    if (stringClass == NULL) {
        return NULL;
    }
    // 找到具體的方法,([C)V 表示選擇 String 的 String(char value[]) 構造方法
    cid = env->GetMethodID(stringClass, "<init>", "([C)V");
    if (cid == NULL) {
        return NULL;
    }
    // 字符串數組做爲參數
    elemArr = env->NewCharArray(len);
    if (elemArr == NULL) {
        return NULL;
    }
    // 給字符串數組賦值
    env->SetCharArrayRegion(elemArr, 0, len, chars);
    // 建立類
    result = (jstring) env->NewObject(stringClass, cid, elemArr);
    env->DeleteLocalRef(elemArr);
    env->DeleteLocalRef(stringClass);
    return result;
}

因爲 String 的構造函數須要傳遞一個字符數組,就先構造好了字符數組並賦值,獲得對應的類和方法 id 以後,直接經過 NewObject 方法調用便可。spa

再來看一個調用自定義類的構造方法的示例,仍是以前的 Animal 類,它的構造方法有一個 String 類型的參數。3d

/**
 * 建立一個 Java 的 Animal 類並返回
 */
extern "C"
JNIEXPORT jobject JNICALL
Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_invokeAnimalConstructors(JNIEnv *env, jobject instance) {
    jclass animalClass;
    jmethodID mid;
    jobject result;
    animalClass = env->FindClass("com/glumes/cppso/model/Animal");
    if (animalClass == NULL) {
        return NULL;
    }
    mid = env->GetMethodID(animalClass, "<init>", "(Ljava/lang/String;)V");
    if (mid == NULL) {
        return NULL;
    }
    jstring args = env->NewStringUTF("this animal name");
    result = env->NewObject(animalClass, mid, args);
    env->DeleteLocalRef(animalClass);
    return result;
}

能夠看到,整個調用流程只要按照步驟來,就能夠了。指針

除了 NewObject 方法以外,JNI 還提供了 AllocObject 方法來建立對象,以一樣調用 Animal 類構造方法爲例:

/**
 * 經過 AllocObject 方法來建立一個類
 */
extern "C"
JNIEXPORT jobject JNICALL
Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_allocObjectConstructor(JNIEnv *env, jobject instance) {
    jclass animalClass;
    jobject result;
    jmethodID mid;
    // 得到對應的 類
    animalClass = env->FindClass("com/glumes/cppso/model/Animal");
    if (animalClass == NULL) {
        return NULL;
    }
    // 得到構造方法 id
    mid = env->GetMethodID(animalClass, "<init>", "(Ljava/lang/String;)V");
    if (mid == NULL) {
        return NULL;
    }
    // 構造方法的參數
    jstring args = env->NewStringUTF("use AllocObject");
    // 建立對象,此時建立的對象未初始化的對象
    result = env->AllocObject(animalClass);
    if (result == NULL) {
        return NULL;
    }
    // 調用 CallNonvirtualVoidMethod 方法去調用類的構造方法
    env->CallNonvirtualVoidMethod(result, animalClass, mid, args);
    if (env->ExceptionCheck()) {
        env->DeleteLocalRef(result);
        return NULL;
    }
    return result;
}

一樣的,要先準備必要的東西。得到對應類的類型、方法 id、構造方法的參數。

而後經過 AllocObject 方法建立對象,但要注意的是,此時建立的對象是未被初始化的,不一樣於 NewObject 方法建立的對象直接就是初始化了,在必定程度上,能夠說 AllocObject 方法是延遲初始化的。

接下來是要經過 CallNonvirtualVoidMethod 來調用對應的構造方法。此處傳入的一個參數再也不是 jclass 類型,而是建立的未被初始化的類 jobject 。

經過這種方法,一樣能夠建立一個 Java 中的類。

調用父類的方法

能夠經過 JNI 來調用父類的實例方法。

在子類中經過調用 CallNonvirtual<Type>Method 方法來調用父類的方法。

首先,構造一個相應的子類,而後得到父類的 類型和方法 id,以及準備對應的參數,根據父類方法的返回值選擇調用不一樣的 CallNonvirtual<Type>Method 函數。

對於引用類型的,調用 CallNonvirtualObjectMethod 方法;對於基礎類型的,調用 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等等;對於無返回值類型的,調用 CallNonvirtualVoidMethod 方法。

具體看代碼:

/**
 * 調用父類的方法
 * 建立一個子類,由子類去調用父類的方法
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_callSuperMethod(JNIEnv *env, jobject instance) {
    jclass cat_cls; // Cat 類的類型
    jmethodID cat_cid; // Cat 類的構造方法 id
    jstring cat_name; // Cat 類的構造方法參數
    jobject cat;
    // 得到對應的 類
    cat_cls = env->FindClass("com/glumes/cppso/model/Cat");
    if (cat_cls == NULL) {
        return;
    }
    // 得到構造方法 id
    cat_cid = env->GetMethodID(cat_cls, "<init>", "(Ljava/lang/String;)V");
    if (cat_cid == NULL) {
        return;
    }
    // 準備構造方法的參數
    cat_name = env->NewStringUTF("this is cat name");
    // 建立 Cat 類
    cat = env->NewObject(cat_cls, cat_cid, cat_name);
    if (cat == NULL) {
        return;
    }
    //調用父類的 getName 參數
    jclass animal_cls; // 父類的類型
    jmethodID animal_mid; // 被調用的父類的方法 id
    // 得到父類對應的類
    animal_cls = env->FindClass("com/glumes/cppso/model/Animal");
    if (animal_cls == NULL) {
        return;
    }
    // 得到父類被調用的方法 id
    animal_mid = env->GetMethodID(animal_cls, "getName", "()Ljava/lang/String;");
    if (animal_mid == NULL) {
        return;
    }
    jstring name = (jstring) env->CallNonvirtualObjectMethod(cat, animal_cls, animal_mid);
    if (name == NULL) {
        return;
    }
    LOGD("getName method value is %s", env->GetStringUTFChars(name, NULL));

    // 調用父類的其餘方法
    animal_mid = env->GetMethodID(animal_cls, "callInstanceMethod", "(I)V");
    if (animal_mid == NULL) {
        return;
    }
    env->CallNonvirtualVoidMethod(cat, animal_cls, animal_mid);
}

Cat 類做爲 Animal 類的子類,首先由 NewObject 方法建立 Cat 類,而後調用它的父類的方法。

由此,經過 JNI 來調用 Java 算是基本完成了。

具體示例代碼可參考個人 Github 項目,歡迎 Star。

https://github.com/glumes/AndroidDevWithCpp

歡迎關注微信公衆號:【紙上淺談】,得到最新文章推送~~

掃碼關注

相關文章
相關標籤/搜索