#yyds干货盘点#Android C++系列:JNI调用 Java 类的构造方法和父类的方法

Posted 轻口味丿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点#Android C++系列:JNI调用 Java 类的构造方法和父类的方法相关的知识,希望对你有一定的参考价值。

#yyds干货盘点#Android

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特性提供了一些思路。

以上是关于#yyds干货盘点#Android C++系列:JNI调用 Java 类的构造方法和父类的方法的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点#Android C++系列:Linux常用函数和工具

#yyds干货盘点#Android C++系列:访问Assets 文件夹

#yyds干货盘点#Android C++系列:Linux Socket编程预备知识

#yyds干货盘点#Android C++系列:JNI调用 Java 类的构造方法和父类的方法

#yyds干货盘点#JavaSE系列Java程序设计基础——数据类型变量与运算符

#yyds干货盘点#JavaScript数组去重方法