Android 还可以通过 JNI 来调用 Java 一个类的构造方法,从而创建一个 Java 类。

调用构造方法

调用构造方法的步骤和之前调用类的实例方法步骤类似,也需要获得对应的类和方法 id。

对于类,通过 FindClass 可以找到对应的 Java 类型。

对于构造方法,它的方法 id 还是通过 ​​GetMethodID​​ 方法来获得,但是构造方法对应的名称为 ,返回值类型是 void 类型的。

完成了以上准备条件后,就可以通过 ​​NewObject​​ 来调用构造方法,从而创建具体的类。

下面以 String 的某个构造方法为例

1public String(char value[]) // Java String 类的其中一个构造方法
2

对应的 C++ 代码:

 1extern "C"
2JNIEXPORT jstring JNICALL
3Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_invokeStringConstructors(JNIEnv *env, jobject instance) {
4
5 jclass stringClass;
6 jmethodID cid;
7 jcharArray elemArr;
8 jstring result;
9
10 // 由 C++ 字符串创建一个 Java 字符串
11 jstring temp = env->NewStringUTF("this is char array");
12 // 再从 Java 字符串获得一个字符数组指针,作为 String 构造函数的参数
13 const jchar *chars = env->GetStringChars(temp, NULL);
14 int len = 10;
15
16 stringClass = env->FindClass("java/lang/String"); // 找到具体的 String 类
17 if (stringClass == NULL) {
18 return NULL;
19 }
20 // 找到具体的方法,([C)V 表示选择 String 的 String(char value[]) 构造方法
21 cid = env->GetMethodID(stringClass, "<init>", "([C)V");
22 if (cid == NULL) {
23 return NULL;
24 }
25 // 字符串数组作为参数
26 elemArr = env->NewCharArray(len);
27 if (elemArr == NULL) {
28 return NULL;
29 }
30 // 给字符串数组赋值
31 env->SetCharArrayRegion(elemArr, 0, len, chars);
32 // 创建类
33 result = (jstring) env->NewObject(stringClass, cid, elemArr);
34 env->DeleteLocalRef(elemArr);
35 env->DeleteLocalRef(stringClass);
36 return result;
37}

由于 String 的构造函数需要传递一个字符数组,就先构造好了字符数组并赋值,得到对应的类和方法 id 之后,直接通过 ​​NewObject​​ 方法调用即可。

再来看一个调用自定义类的构造方法的示例,还是之前的 Animal 类,它的构造方法有一个 String 类型的参数。

1/**
2 * 创建一个 Java 的 Animal 类并返回
3 */
4extern "C"
5JNIEXPORT jobject JNICALL
6Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_invokeAnimalConstructors(JNIEnv *env, jobject instance) {
7 jclass animalClass;
8 jmethodID mid;
9 jobject result;
10 animalClass = env->FindClass("com/glumes/cppso/model/Animal");
11 if (animalClass == NULL) {
12 return NULL;
13 }
14 mid = env->GetMethodID(animalClass, "<init>", "(Ljava/lang/String;)V");
15 if (mid == NULL) {
16 return NULL;
17 }
18 jstring args = env->NewStringUTF("this animal name");
19 result = env->NewObject(animalClass, mid, args);
20 env->DeleteLocalRef(animalClass);
21 return result;
22}

可以看到,整个调用流程只要按照步骤来,就可以了。

除了 NewObject 方法之外,JNI 还提供了 AllocObject 方法来创建对象,以同样调用 Animal 类构造方法为例:

1/**
2 * 通过 AllocObject 方法来创建一个类
3 */
4extern "C"
5JNIEXPORT jobject JNICALL
6Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_allocObjectConstructor(JNIEnv *env, jobject instance) {
7 jclass animalClass;
8 jobject result;
9 jmethodID mid;
10 // 获得对应的 类
11 animalClass = env->FindClass("com/glumes/cppso/model/Animal");
12 if (animalClass == NULL) {
13 return NULL;
14 }
15 // 获得构造方法 id
16 mid = env->GetMethodID(animalClass, "<init>", "(Ljava/lang/String;)V");
17 if (mid == NULL) {
18 return NULL;
19 }
20 // 构造方法的参数
21 jstring args = env->NewStringUTF("use AllocObject");
22 // 创建对象,此时创建的对象未初始化的对象
23 result = env->AllocObject(animalClass);
24 if (result == NULL) {
25 return NULL;
26 }
27 // 调用 CallNonvirtualVoidMethod 方法去调用类的构造方法
28 env->CallNonvirtualVoidMethod(result, animalClass, mid, args);
29 if (env->ExceptionCheck()) {
30 env->DeleteLocalRef(result);
31 return NULL;
32 }
33 return result;
34}

同样的,要先准备必要的东西。获得对应类的类型、方法 id、构造方法的参数。

然后通过 ​​AllocObject​​​ 方法创建对象,但要注意的是,此时创建的对象是未被初始化的,不同于 ​​NewObject​​​ 方法创建的对象直接就是初始化了,在一定程度上,可以说 ​​AllocObject​​ 方法是延迟初始化的。

接下来是要通过 ​​CallNonvirtualVoidMethod​​ 来调用对应的构造方法。此处传入的一个参数不再是 jclass 类型,而是创建的未被初始化的类 jobject 。

通过这种方法,同样可以创建一个 Java 中的类。

调用父类的方法

可以通过 JNI 来调用父类的实例方法。

在子类中通过调用 ​​CallNonvirtual<Type>Method​​ 方法来调用父类的方法。

首先,构造一个相应的子类,然后获得父类的 类型和方法 id,以及准备对应的参数,根据父类方法的返回值选择调用不同的 ​​CallNonvirtual<Type>Method​​  函数。

对于引用类型的,调用 CallNonvirtualObjectMethod 方法;对于基础类型的,调用 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等等;对于无返回值类型的,调用 CallNonvirtualVoidMethod 方法。

具体看代码:

1/**
2 * 调用父类的方法
3 * 创建一个子类,由子类去调用父类的方法
4 */
5extern "C"
6JNIEXPORT void JNICALL
7Java_com_glumes_cppso_jnioperations_InvokeConstructorOps_callSuperMethod(JNIEnv *env, jobject instance) {
8 jclass cat_cls; // Cat 类的类型
9 jmethodID cat_cid; // Cat 类的构造方法 id
10 jstring cat_name; // Cat 类的构造方法参数
11 jobject cat;
12 // 获得对应的 类
13 cat_cls = env->FindClass("com/glumes/cppso/model/Cat");
14 if (cat_cls == NULL) {
15 return;
16 }
17 // 获得构造方法 id
18 cat_cid = env->GetMethodID(cat_cls, "<init>", "(Ljava/lang/String;)V");
19 if (cat_cid == NULL) {
20 return;
21 }
22 // 准备构造方法的参数
23 cat_name = env->NewStringUTF("this is cat name");
24 // 创建 Cat 类
25 cat = env->NewObject(cat_cls, cat_cid, cat_name);
26 if (cat == NULL) {
27 return;
28 }
29 //调用父类的 getName 参数
30 jclass animal_cls; // 父类的类型
31 jmethodID animal_mid; // 被调用的父类的方法 id
32 // 获得父类对应的类
33 animal_cls = env->FindClass("com/glumes/cppso/model/Animal");
34 if (animal_cls == NULL) {
35 return;
36 }
37 // 获得父类被调用的方法 id
38 animal_mid = env->GetMethodID(animal_cls, "getName", "()Ljava/lang/String;");
39 if (animal_mid == NULL) {
40 return;
41 }
42 jstring name = (jstring) env->CallNonvirtualObjectMethod(cat, animal_cls, animal_mid);
43 if (name == NULL) {
44 return;
45 }
46 LOGD("getName method value is %s", env->GetStringUTFChars(name, NULL));
47
48 // 调用父类的其他方法
49 animal_mid = env->GetMethodID(animal_cls, "callInstanceMethod", "(I)V");
50 if (animal_mid == NULL) {
51 return;
52 }
53 env->CallNonvirtualVoidMethod(cat, animal_cls, animal_mid);
54}

Cat 类作为 Animal 类的子类,首先由 NewObject 方法创建 Cat 类,然后调用它的父类的方法。

由此,通过 JNI 来调用 Java 算是基本完成了。

具体示例代码可参考我的 Github 项目,欢迎 Star。

​https://github.com/glumes/AndroidDevWithCpp​

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

Android 通过 JNI 调用 Java 类的构造方法和父类的方法_构造方法扫码关注