JNI创建Java对象
概述
当我们在native方法中调用一个Java对象的方法时,如果这个Java方法需要一个Java对象作为参数,那么我们要在JNI中创建相应类型的对象。例如如果Java方法需要String作为参数,那么在native方法中需要创建一个jstring对象,然后才能作为参数调用Java方法。
因此,了解如何在JNI中创建Java对象还是很有必要的。另外本文还会对Class操作以及Object操作进行一些总结,例如判断两个对象是否相等,或者一个对象是否是一个Class的实例。
创建Java对象
在Java创建一个对象非常简单,只需要使用new操作符,然而在JNI中创建Java类对应的对象,需要三步
- 使用Java类的全路径创建一个jclass对象。
- 使用jclass对象调用Java类的构造函数.
- 利用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相关知识还有更高的认识,我会继续更新文章。
转载自:https://juejin.cn/post/6934888365506428942