JNI创建Java对象

3,288 阅读4分钟

概述

当我们在native方法中调用一个Java对象的方法时,如果这个Java方法需要一个Java对象作为参数,那么我们要在JNI中创建相应类型的对象。例如如果Java方法需要String作为参数,那么在native方法中需要创建一个jstring对象,然后才能作为参数调用Java方法。

因此,了解如何在JNI中创建Java对象还是很有必要的。另外本文还会对Class操作以及Object操作进行一些总结,例如判断两个对象是否相等,或者一个对象是否是一个Class的实例。

创建Java对象

在Java创建一个对象非常简单,只需要使用new操作符,然而在JNI中创建Java类对应的对象,需要三步

  1. 使用Java类的全路径创建一个jclass对象。
  2. 使用jclass对象调用Java类的构造函数.
  3. 利用jclass和构造函数创建一个jobject对象,指向的就是Java对象。

第一步中,创建jclass对象,我们经常使用如下函数

jclass FindClass(JNIEnv *env, const char *name);

参数name为Java类的全路径。如果无法找到对应的Java class,则返回NULL。

还有另外一个函数可以获取jcalss对象,这个函数是从一个Java对象中获取的

jclass GetObjectClass(JNIEnv *env, jobject obj);

第二步是要调用类的构造函数,根据前面的文章可知,首先要找到这个构造函数的jmethodID,但是这个构造函数的名字是什么呢?这里就要注意了,所有类的构造函数的名字统一为<init>

第三步,使用jclass和构造函数创建jobject对象,常用的函数如下

jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

clazz 就是第一步获取的jclass对象,methoID就是第二步获取的jmethodID对象,后面的...表示可变参数,构造函数需要几个函数,这里就是传入几个参数,如果没有,就不需要传入参数。

下面来演示下如何在native函数中创建一个Person对象,并返回给Java层。

首先看下Java的Person类的定义

public class Person {
    private int age;
    private String name;
    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

native创建Person对象的代码如下

extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_helllojni_MainActivity_getPersonFromJNI(JNIEnv *env, jobject thiz) {
    // 1. 获取Person对应的jlcass对象
    jclass person_clazz = env->FindClass("com/example/helllojni/Person");
    if (person_clazz == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Couldn't find com/example/helllojni/Person");
        return nullptr;
    }
    
    // 2. 获取构造函数的jmethodID
    jmethodID person_constructor = env->GetMethodID(person_clazz, "<init>",
                                                    "(Ljava/lang/String;I)V");
    if (person_constructor == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Couldn't find Person(String name, int age) constructor.");
        return nullptr;
    }
    
    // Person构造函数需要一个String参数,所以需要创建一个jstring
    jstring name = env->NewStringUTF("David");
    
    // 3. 调用构造函数创建Person对象,并返回给Java层
    return env->NewObject(person_clazz, person_constructor, name, 18);
}

在这段代码中,如何获取jmethoID,如何获取jstring,这些江西在我前面的文章中都已经讲过,这里就不再解释了。

另外需要注意下这里的判空操作,由于JNI的Bug很难找到,因此在JNI中加入相应的log是一件非常重要的事情,大家不要忽视这个。

Class操作

本文的这一部分,列举一些Class经常使用的操作的函数。

jclass GetSuperclass(JNIEnv *env, jclass clazz);

这个函数返回一个类的父类,Object类没有父类,因此返回NULL。

jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

这个函数用于判断clazz1的对象是否能安全转换到clazz2的对象。假如Circle继承自Shape类,那么Circle类的对象就能安全转换到Shape的对象上。在Java中基类的对象可以指向派生类对象,接口对象可以指向实现类对象,这个函数判断的关系就是这个。

Object 操作

jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);

这个函数用于判断 obj 是否是 clazz 的对象。

jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);

这个函数用于判断两个引用指向的对象是否相等。在Java中使用==判断两个对象相等。

感想

关于JNI文章,我本来打算连载的,但是中间由于工作的原因中断了。从这篇文章后,关于JNI大部分常用操作,已经讲的差不多了,但是还有一些用的极少的操作(例如NIO),由于我在工作中没有接触到,因此也不敢随便写。所以这篇文章算是JNI最后一篇文章了,如果日后对JNI相关知识还有更高的认识,我会继续更新文章。