写点什么

Android C++ 系列:JNI 调用 Java 类的构造方法和父类的方法.md

用户头像
轻口味
关注
发布于: 2 小时前
Android C++系列:JNI调用 Java 类的构造方法和父类的方法.md

Android JNI 开发时经常遇到 C/C++层访问 Java 层对象的,比如 C/C++层创建一个 String 返回,或者访问 Java 层提供的 MediaCodec 等,此时我们就需要通过 JNI 来调用 Java 一个类的构造方法来创建这个 Java 类。

调用构造方法

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


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

  • 对于构造方法,通过 GetMethodID 方法来获取它的方法 ID,不同的是构造方法对应的名称为<init> ,返回值类型是 void 。

  • 通过 NewObject 传入获取到的构造方法 id 来调用构造方法创建具体的类。


下面以 String 的字符数组构造方法为例在 C/C++层实现对象的创建。


public String(char value[])
复制代码


对应的 C++ 代码:


extern "C" JNIEXPORT jstring JNICALL Java_com_qingkouwei_demo_InvokeDemo_invokeStringConstructors(JNIEnv *env, jobject instance) {
jclass stringClass; jmethodID cid; jcharArray elemArr; jstring result;
// 创建string字符串 jstring temp = env->NewStringUTF("this is char array"); // jstring字符串转换为字节数组,作为构造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); // 使用NewObject创建类 result = (jstring) env->NewObject(stringClass, cid, elemArr); env->DeleteLocalRef(elemArr); env->DeleteLocalRef(stringClass); return result; }
复制代码


我们先构造好字符数组,并赋值,再获取 String 的 jclass 以及构造方法的方法 id,通过 NewObject 传入字符数组来调用 Sting 的构造函数创建 String 对象。


其他的不管是 Android 系统提供的类还是自定义的各种 Java 类,都可以通过上述流程来创建。


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


NewObject 其实是一个方法做了两件事情:


  1. 创建类对象;

  2. 调用构造方法。


Jni 提供了 AllocObject 创建对象和 CallNonvirtualVoidMethod 调用构造方法来分步完成 Java 对象的构建,以我们自定义的一个简单类为例:


 /**  * 通过 AllocObject 方法来创建一个类  */ extern "C" JNIEXPORT jobject JNICALL Java_com_qingkouwei_demo_InvokeDemo_allocObjectConstructor(JNIEnv *env, jobject instance) {     jclass fruitClass;     jobject result;     jmethodID mid;     // 获得对应的Fruit类     fruitClass = env->FindClass("com/qingkouwei/demo/bean/Fruit");     if (fruitClass == NULL) {//为空容错判断         return NULL;     }     // 获得Fruit构造方法id     mid = env->GetMethodID(fruitClass, "<init>", "(Ljava/lang/String;)V");     if (mid == NULL) {//为空容错判断         return NULL;     }     // 构造方法的参数     jstring args = env->NewStringUTF("creat fruit use AllocObject");     // 创建未被初始化的对象     result = env->AllocObject(fruitClass);     if (result == NULL) {//为空容错判断         return NULL;     }     //使用CallNonvirtualVoidMethod 方法调用类的构造方法     env->CallNonvirtualVoidMethod(result, fruitClass, mid, args);     if (env->ExceptionCheck()) {//检测创建过程中是否有异常         env->DeleteLocalRef(result);         return NULL;     }     return result; }
复制代码


这里实现了对象创建和初始化分离的实现,在 Java 中没有为我们提供的方法,在 JNI 层提供了。

调用父类的方法

我们知道 Java 的一大特定是多态,类是可以继承或者被继承的,那么问题来了,在 JNI 中创建 Java 对象时如何调用父类的方法呢?


我们可以在子类中通过调用 CallNonvirtualMethod 方法来调用父类的方法。


构造一个相应的子类,然后获得父类的 类型和方法 id,根据父类方法的返回值选择调用不同的 CallNonvirtualMethod 函数:


  1. 对于引用类型的,调用 CallNonvirtualObjectMethod 方法;

  2. 对于基础类型的,调用 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等等;

  3. 对于无返回值类型的,调用 CallNonvirtualVoidMethod 方法。


我们实现一个在苹果中调用水鬼的 getName 的方法:


/**  * JNI层中创建子类并调用父类方法  */ extern "C" JNIEXPORT void JNICALL Java_com_qingkouwei_demo_Demo_InvokeDemo_callSuperMethod(JNIEnv *env, jobject instance) {     jclass apple_cls; // Apple 类的类型     jmethodID apple_cid; // Apple 类的构造方法 id     jstring apple_name; // Apple 类的构造方法参数     jobject apple;     // 获得对应的 类     apple_cls = env->FindClass("com/qingkouwei/demo/bean/Apple");     if (apple_cls == NULL) {         return;     }     // 获得构造方法 id     apple_cid = env->GetMethodID(apple_cls, "<init>", "(Ljava/lang/String;)V");     if (apple_cid == NULL) {         return;     }     // 准备构造方法的参数     apple_name = env->NewStringUTF("this is apple name");     // 创建 Apple 类     apple = env->NewObject(apple_cls, apple_cid, apple_name);     if (apple == NULL) {         return;     }     //调用父类的 getName 参数     jclass fruit_cls; // 父类的类型     jmethodID fruit_mid; // 被调用的父类的方法 id     // 获得父类对应的类     fruit_cls = env->FindClass("com/qingkouwei/demo/bean/Fruit");     if (fruit_cls == NULL) {         return;     }     // 获得父类被调用的方法 id     fruit_mid = env->GetMethodID(fruit_cls, "getName", "()Ljava/lang/String;");     if (fruit_mid == NULL) {         return;     }     jstring name = (jstring) env->CallNonvirtualObjectMethod(apple, fruit_cls, fruit_mid);     if (name == NULL) {         return;     }     //print     LOGI("getName method value is %s", env->GetStringUTFChars(name, NULL)); }
复制代码


Apple 作为 Fruit 的子类,首先通过 NewObject 构建子类 Apple 对象,然后再获取父类 Fruit 的 jclass 以及方法 id,通过 CallNonvirtualObjectMethod 调用父类 Fruit 的 getName 方法。

小结

本文讲解了 JNI 层创建 Java 层对象的两种方法(通过 NewObject 一次性创建和通过 AllocObject 和 CallNonvirtualVoidMethod 分布创建的方法)和如何在 JNI 层调用 Java 层类对象的父类方法的方法。对一些复杂的项目使用 JNI 特性提供了一些思路。

发布于: 2 小时前阅读数: 5
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:JNI调用 Java 类的构造方法和父类的方法.md