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

欢迎关注微信公众号:【纸上浅谈】,得到最新文章推送~~

扫码关注

相关文章
相关标签/搜索