Android JNI开发-对象操作
对象操作基本步骤
Jni是沟通Java世界和Native世界的纽带,Java层调用本地方法只用调用Java中定义的本地(native
)方法就可用了,那么,本地的C/C++代码如何调用Java层的代码呢?这就是本章节对象操作
要解决阐述的内容。
一般的,C/C++层要调用Java层代码,需要进行以下步骤。
- 获取Java层对应的
jclass
,通过jclass
来获取Java类的方法,属性。 - 获取Java层对象引用,如果Java层有传引用下来,则可用直接使用,否则就需要在C/C++层调用创建对象的接口来创建Java层对应类的对象。调用Java静态函数不需要对象引用。
- 调用Java层类的静态方法,或者Java层对象的方法。
- 处理返回值。
函数列表
Jni中类操作相关的函数包括以下函数:
函数 | 说明 |
---|---|
DefineClass | 从原始类数据的缓冲区中加载类 |
FindClass | 查找类 |
GetSuperclass | 查找父类 |
IsAssignableFrom | 类型判断 |
关于类名
Jni在加载或查找Java层类时,需要通过类的全名来进行加载,该类的全名为类包含包名的全路径,比如 java.lang.String
类,那么类的全名则为java/lang/String
;
对应的C++函数定义如下:
/**
* 从原始类数据的缓冲区中加载类
* @param name 类的全名,必须是被UTF-8编码过的字符串。
* @param loader 类加载器
* @param buf 包含 .class 文件数据的缓冲区
* @return java类对象。发生错误时返回NULL
*/
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
/**
* 查找类
* @param name 类的全名,必须是被UTF-8编码过的字符串。
* @return
*/
jclass FindClass(const char* name)
/**
* 获取父类
* @param clazz Java类对象
* @return java类对象。clazz的父类或NULL
*/
jclass GetSuperclass(jclass clazz)
/**
* 确定clazz1的对象是否可安全地强制转换为clazz2
* @param clazz1 类的对象
* @param clazz2 类的对象
* @return 如果满足以下任一条件,则返回JNI_TRUE:
* 1. 如果clazz1和clazz2是同一个Java类。
* 2. 如果clazz1是clazz2的子类
* 3. 如果clazz1是clazz2接口的实现类
*/
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)
访问对象的变量
准备工作
public class TestCallJavaObject {
public native void putString(String hello); // 传递String对象
public native void putObject(Student student); // 传递引用类型,传递对象
public native void insertObject(); // 凭空创建Java对象
public static void main(String[] args) {
}
}
Javah 生成native方法头文件,可以在idea中配置好工具
///------------------???calljava.cpp???-------------------
#include "com_jni_calljava_TestCallJavaObject.h"
#include <string>
#include <iostream>
using namespace std;
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_putObject
(JNIEnv *env, jobject thiz, jobject student) {
// 1.寻找类 Student
jclass studentClass1 = env->FindClass("com/jni/calljava/Student"); // 第一种
jclass studentClass2 = env->GetObjectClass(student); // 第二种
cout << "studentClass1=" << studentClass1 << endl;
cout << "studentClass1=" << studentClass2 << endl;
}
public static void main(String[] args) {
TestCallJavaObject testCallJavaObject = new TestCallJavaObject();
Student student = new Student();
testCallJavaObject.putObject(student);
}
RUN>
*********************** ?运行结果? ************************** studentClass1=0x7ff60a80dad0 studentClass1=0x7ff60a80dad8
可以看到env->FindClass
和env->GetObjectClass(student)
两种方法获取的jclass对象内存地址一致,说明获取到的都是同一个对象,如果你懂Java
反射,那么自然懂得获取的java对象的class对象,是反射的基础.
获取jmethodID
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
参数
-
jclass clazz
:Java
类的Class
对象,需要通过GetObjectClass()
或者FindClass()
获取。 -
const char * name
: 方法名,需要通过GetMethodID()
获取。 -
const char * sig
: 方法签名。
返回值
- 如果找到了对应的方法,就会返回一个
jmethodID
对象,否则返回NULL
。
注意: 如果要获取
Java
类的构造方法,参数const char *name
的值为<init>
,参数const char *sig
的值为void (V)
。这个是比较特殊的,需要注意。
调用对象的方法
根据Java
方法返回的类型的不同,JNI
有不同的函数来调用Java
对象的方法。不过基本形式有三种
// C 函数原型
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
// C++ 函数原型
jobject CallObjectMethod(jobject, jmethodID, ...);
jobject CallObjectMethodV(jobject, jmethodID, va_list);
jobject CallObjectMethodA(jobject, jmethodID, const jvalue*);
可以看到这三类函数的区别在于传参的方式不同。
第一个函数和第三个函数其实原理都是一样的,不过最常用的应该就是第一个。
那么第二个函数就有点意思了,参数使用的是jvalue
类型的数组作为参数。
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
jvalue
是一个联合体,联合体中定义Java
基本类型和引用类型的变量,因此就可以以数组的行为来传递参数,这下就明白了吧。
获取jfieldID
jfieldID GetFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
参数
-
env
: 指向JNIEnv
的指针。 -
clazz
:Class
对象,可以通过GetObjectClass()
或者FindClass()
获取。 -
name
:Class
对象的某个变量的名字。 -
sig
:Class
对象的变量的类型签名。
返回一个Class
对象的变量,类型为jfieldID
。Class
对象的变量由参数name
和sig
唯一标识。
与Java
反射一样,获取到了变量后,就可以通过这个变量获取到变量的值,也可以设置变量的值。
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_putObject
(JNIEnv *env, jobject thiz, jobject student) {
// 1.寻找类 Student
cout<<"---------寻找类 Student---------------"<< endl;
jclass studentClass1 = env->FindClass("com/jni/calljava/Student"); // 第一种
jclass studentClass2 = env->GetObjectClass(student); // 第二种
cout << "studentClass1=" << studentClass1 << endl;
cout << "studentClass1=" << studentClass2 << endl;
cout<<"-----------GetMethodID------------"<< endl;
// 2.Student类里面的函数规则 签名
jmethodID setName = env->GetMethodID(studentClass1, "setName", "(Ljava/lang/String;)V");
jmethodID getName = env->GetMethodID(studentClass1, "getName", "()Ljava/lang/String;");
jmethodID showInfo = env->GetStaticMethodID(studentClass1, "showInfo", "(Ljava/lang/String;)V");
// 3.调用 setName
cout<<"-----------CallVoidMethod------------"<< endl;
jstring value = env->NewStringUTF("AAAA");
env->CallVoidMethod(student, setName, value);
// 4.调用 getName
jstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));
const char *getNameValue = env->GetStringUTFChars(getNameResult, NULL);
cout << ("调用到getName方法,值是:") << getNameValue << endl;
// 5 访问对象的变量
cout<<"------访问对象的变量---------"<< endl;
jfieldID fieldID_mAge = env->GetFieldID(studentClass1, "age", "I");
// 设置Java对象obj的变量mAge的值
env->SetIntField(student, fieldID_mAge, 18);
// 从Java对象obj中获取变量mAge的值
jint age = env->GetIntField(student, fieldID_mAge);
cout << "访问对象的变量 Age: " << age << std::endl;
}
package com.jni.calljava;
public class Student {
public String name;
public int age;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Java setName name:" + name);
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
System.out.println("Java setAge age:" + age);
this.age = age;
}
public static void showInfo(String info) {
System.out.println("showInfo info:" + info);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
RUN>
*********************** ?运行结果? ************************** ---------寻找类 Student--------------- studentClass1=0x7faba2d0a6f0 studentClass1=0x7faba2d0a6f8 -----------GetMethodID------------ -----------CallVoidMethod------------ Java setName name:AAAA 调用到getName方法,值是:AAAA ------访问对象的变量--------- 访问对象的变量 Age: 18
对象操作
Jni中对象操作相关的函数主要包括:
函数 | 说明 |
---|---|
AllocObject | 直接创建Java对象 |
NewObject | 根据某个构造函数来创建Java对象 |
NewObjectV | 根据某个构造函数来创建Java对象 |
NewObjectA | 根据某个构造函数来创建Java对象 |
GetObjectClass | 获取指定类对象的类 |
IsInstanceOf | 判断某个对象是否是某个“类”的子类 |
对应的C++函数定义如下:
/**
* 不借助任何构造函数的情况下分配一个新的Java对象
* @param clazz Java类对象
* @return 返回一个Java对象实例,如果该对象无法被创建,则返回NULL
*/
jobject AllocObject(jclass clazz)
/**
* 根据构造函数来创建Java对象
* @param clazz Java类对象
* @param methodID 构造函数的方法ID
* @param ... 构造函数的输入参数(可变参数)
* @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
*/
jobject NewObject(jclass clazz, jmethodID methodID, ...)
/**
* 根据构造函数来创建Java对象
* @param clazz Java类对象
* @param methodID 构造函数的方法ID
* @param args 构造函数的输入参数(va_list)
* @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
*/
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
/**
* 根据构造函数来创建Java对象
* @param clazz Java类对象
* @param methodID 构造函数的方法ID
* @param args 构造函数的输入参数(参数数组)
* @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
*/
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args)
/**
* 通过对象获取类
* @param obj 类的对象
* @return Java 类对象。
*/
jclass GetObjectClass(jobject obj)
/**
* 判断某个对象是否是某个“类”的子类
* @param obj Java对象实例
* @param clazz Java类对象
* @return
*/
jboolean IsInstanceOf(jobject obj, jclass clazz)
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_insertObject
(JNIEnv *env, jobject thiz) {
// 1.通过包名+类名的方式 拿到 Student class 凭空拿class
jclass studentClass = env->FindClass("com/jni/calljava/Student"); // 第一种
// 2.通过student的class 实例化此Student对象 C++ new Student
jobject studentObj = env->AllocObject(studentClass); // AllocObject 只实例化对象,不会调用对象的构造函数
// 方法签名的规则
jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");
// 调用方法
jstring strValue = env->NewStringUTF("HelloJni");
env->CallVoidMethod(studentObj, setName, strValue);
env->CallVoidMethod(studentObj, setAge, 18);
jmethodID toStringMethod = env->GetMethodID(studentClass, "toString", "()Ljava/lang/String;");
jstring toStringResult = static_cast<jstring>(env->CallObjectMethod(studentObj, toStringMethod));
const char *value = env->GetStringUTFChars(toStringResult, nullptr);
cout << value << endl;
env->DeleteLocalRef(studentClass);
env->DeleteLocalRef(studentObj);
}
RUN>
*********************** ?运行结果? ************************** Java setName name:HelloJni Java setAge age:18 Student{name='HelloJni', age=18}
jclass dogClass;
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_testQuote
(JNIEnv *env, jobject thiz) {
if (nullptr == dogClass) {
// 升级全局引用: JNI函数结束也不释放,反正就是不释放,必须手动释放 ----- 相当于: C++ 对象 new、手动delete
const char *dogStr = "com/jni/calljava/Dog";
jclass temp = env->FindClass(dogStr);
dogClass = static_cast<jclass>(env->NewGlobalRef(temp)); // 提升全局引用
// 记住:用完了,如果不用了,马上释放,C C++ 工程师的赞美
env->DeleteLocalRef(temp);
}
// <init> V 是不会变的
// 构造函数一
jmethodID init = env->GetMethodID(dogClass, "<init>", "()V");
jobject dog = env->NewObject(dogClass, init);
// 构造函数2
init = env->GetMethodID(dogClass, "<init>", "(I)V");
dog = env->NewObject(dogClass, init, 100);
// 构造函数3
init = env->GetMethodID(dogClass, "<init>", "(II)V");
dog = env->NewObject(dogClass, init, 200, 300);
// 构造函数4
init = env->GetMethodID(dogClass, "<init>", "(III)V");
dog = env->NewObject(dogClass, init, 400, 500, 600);
env->DeleteLocalRef(dog); // 释放
}
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_delQuote
(JNIEnv *env, jobject thiz) {
if (dogClass != nullptr) {
cout<<("全局引用释放完毕~~~~~~~~~????");
env->DeleteGlobalRef(dogClass);
dogClass = nullptr; // 最好给一个NULL,指向NULL的地址,不要去成为悬空指针,为了好判断悬空指针的出现
}
}
RUN>
*********************** ?运行结果? ************************** Dog init... Dog init... n1:100 Dog init... n1:200 n2:300 Dog init... n1:400 n2:500 n3:600 全局引用释放完毕~~~~~~~~~????
代码传送门
Demo-GitHub