通过JNI层,本地层可间接操作java 类和实例的属性和方法,包括实例化类、set/get属性值、调用方法等。由于涉及的接口比较多,我们按功能进行分类说明。
1 JNI获取Java类的java.lang.Class实例
(1)涉及到的JNI接口
共有4个接口可用于获取Class实例。
接口 | 功能 |
DefineClass | 根据java 类文件的二进制数据,获取其Class实例 |
FindClass | 根据java类的 全限定名 找到其Class实例 |
GetSuperclass | 根据java 子类的Class实例 找到父类的Class实例 |
GetObjectClass | 根据java 实例 找到其类的Class实例 |
注意:类的全限定名 即为包名 + 类名,表示形式为:
com.example.jnilearn.LearnJNI 或
com/example/jnilearn/LearnJNI
区别仅在于用什么符号分隔,JNI中用第二种表示方法,而java中一般用第一种表示方法。
注意: Class类 指的是java层 java.lang.Class 类。
(2)接口详解
//TODO:待学习
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen);
/**
* 作用:根据java类的全限定名,获取该类的Class实例引用
* @param name 类的全限定名,如:com/example/jnilearn/LearnJni
* @return Class实例引用;若没找到或报错,则返回null
*
* @exceptions 该方法可能抛出以下异常:
* ClassFormatError: 类数据的格式不符合java标准格式
* ClassCircularityError: 类或接口自己继承自己
* NoClassDefFoundError: 找不到指定类
* OutOfMemoryError: 空间不足
* ExceptionInInitializerError: 初始化类时出错。即加载类时,执行static的属性、方法块时,出错了。
*/
jclass FindClass(const char* name);
/**
* 作用:根据java子类的Class实例 找到父类的Class实例
* @param clazz java类的Class实例引用
* @return 返回java父类的Class实例引用,若clazz代表接口或者没有父类,则返回null。
*
* @exceptions 该方法不会抛出异常
*/
jclass GetSuperclass(jclass clazz);
/**
* 作用:根据java实例 获取其类的Class实例
* @param obj java实例引用
* @return Class实例引用
*
* @exceptions 该方法不会抛出异常
*/
jclass GetObjectClass(jobject obj);
(3)接口使用
获取java Class实例
jclass getJavaClassByName(JNIEnv * env){
jclass cls_LearnJNI = env->FindClass("com/example/jnilearn/LearnJNI");
}
jclass getJavaClassByObj(JNIEnv * env,jobject learnJNI){
jclass cls_LearnJNI = env->GetObjectClass(learnJNI);
}
jclass getJavaClassBySon(JNIEnv * env,jclass clazz){
jclass cls_LearnJNI = env->GetSuperclass(clazz);
}
2 JNI操作java实例或类属性
通过JNI,可以获取或设置java基本类型及引用类型 属性的值。
(1)涉及到的JNI接口
共涉及到34个JNI接口,按照功能看,其实只有6个接口,Type当做函数名的占位符,可以是Short、Int、Long、Float、Double、Boolean、Byte、Char、Object。
接口 | 功能 |
GetFieldID | 获取用jfield类型表示的java实例属性ID |
GetStaticFieldID | 获取用jfield类型表示的java类属性ID |
Get<Type>Field | 获取java实例属性值 |
Set<Type>Field | 设置java实例属性值 |
GetStatic<Type>Field | 获取java类属性值 |
SetStatic<Type>Field | 设置java类属性值 |
(2)接口详解
/**
* 作用:获取JNI定义的java实例属性ID
* @param clazz 实例属性所属类的Class实例
* @param name 实例属性名字
* @param sig 实例属性签名
* @return 实例属性ID
*
* @exceptions 该方法可能抛出以下异常
* NoSuchFieldError 没找到指定实例属性
* ExceptionInInitializerError 初始化类时出错。即加载类时,执行static的属性、方法块时,出错了
* OutOfMemoryError 内存溢出
*/
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
/**
* 作用:获取JNI定义的java类属性ID
* @param clazz 类属性所在类的Class实例
* @param name 类属性名字
* @param sig java类属性签名
* @return 类属性ID
*
* @exceptions 该方法可能抛出以下异常
* NoSuchFieldError 没找到指定实例属性
* ExceptionInInitializerError 初始化类时出错。即加载类时,执行static的属性、方法块时,出错了
* OutOfMemoryError 内存溢出
*/
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig);
/**
* 作用:获取java实例属性值
* @param obj java实例
* @param fieldID JNI定义的java实例属性ID
* @return 实例属性值
*
* @exceptions 这类方法不会抛出异常
*/
jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID);
jbyte GetByteField(jobject obj, jfieldID fieldID);
jchar GetCharField(jobject obj, jfieldID fieldID);
jshort GetShortField(jobject obj, jfieldID fieldID);
jint GetIntField(jobject obj, jfieldID fieldID);
jlong GetLongField(jobject obj, jfieldID fieldID);
jfloat GetFloatField(jobject obj, jfieldID fieldID);
jdouble GetDoubleField(jobject obj, jfieldID fieldID);
/**
* 作用:设置java实例属性值
* @param obj java实例
* @param fieldID JNI定义的java实例属性ID
* @param value 要设置的值
*
* @exceptions 这类方法不会抛出异常
*/
void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value);
void SetByteField(jobject obj, jfieldID fieldID, jbyte value);
void SetCharField(jobject obj, jfieldID fieldID, jchar value);
void SetShortField(jobject obj, jfieldID fieldID, jshort value);
void SetIntField(jobject obj, jfieldID fieldID, jint value);
void SetLongField(jobject obj, jfieldID fieldID, jlong value);
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value);
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value);
/**
* 作用:获取java类属性值
* @param clazz java类的Class实例
* @param fieldID JNI定义的java类属性ID
* @return 类属性值
*
* @exceptions 这类方法不会抛出异常
*/
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID);
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID);
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID);
jchar GetStaticCharField(jclass clazz, jfieldID fieldID);
jshort GetStaticShortField(jclass clazz, jfieldID fieldID);
jint GetStaticIntField(jclass clazz, jfieldID fieldID);
jlong GetStaticLongField(jclass clazz, jfieldID fieldID);
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID);
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID);
/**
* 作用:设置java类属性值
* @param clazz java类的Class实例
* @param fieldID JNI定义的java类属性ID
* @param value 要设置的值
*
* @exceptions 这类方法不会抛出异常
*/
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value);
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value);
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value);
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value);
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value);
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value);
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value);
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value);
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value);
(3)接口使用
以操作java String类型(引用类型)实例属性及类属性为例进行说明,操作java基本类型属性的方法与之基本相同。
/**
* 获取java对象String类型的实例属性
* @param env JNI接口指针
* @param obj java对象
* @param field_name 实例属性名称
* @return 实例属性值
*/
jstring getStringField(JNIEnv * env,jobject obj,const char* field_name){
//通过java对象,获取该对象对应类的Class实例
jclass cls = env->GetObjectClass(obj);
//获取实例属性ID
jfieldID fid = env->GetFieldID(cls,field_name,"Ljava/lang/String;");
//获取对象的实例属性值
jstring field_value = (jstring)env->GetObjectField(obj,fid);
return field_value;
}
/**
* 给java对象String类型的实例属性赋值
* @param env JNI接口指针
* @param obj java对象
* @param field_name 实例属性名称
* @param value C/C++字符串。注意:也可以为以char数组,但必须以 \0 结尾,否则报错;
* @return
*/
void setStringField(JNIEnv * env,jobject obj,const char* field_name,char* value){
//通过java对象,获取该对象对应类的Class实例
jclass cls = env->GetObjectClass(obj);
//获取实例属性ID
jfieldID fid = env->GetFieldID(cls,field_name,"Ljava/lang/String;");
//设置属性值
env->SetObjectField(obj,fid,env->NewStringUTF(value));
}
/**
* 获取java类的类属性(静态属性)
* @param env JNI接口指针
* @param jclazz java类的Class实例
* @param field_name 类属性名称
* @return 类属性值
*/
jstring getStaticStringField(JNIEnv * env,jclass jclazz,const char* field_name){
//获取类属性ID
jfieldID fid = env->GetStaticFieldID(jclazz,field_name,"Ljava/lang/String;");
//获取类属性值
jstring field_value = (jstring)env->GetStaticObjectField(jclazz,fid);
return field_value;
}
/**
* 给java类的类属性赋值
* @param env JNI接口指针
* @param jclazz java类的Class实例
* @param field_name 类属性名称
* @param value C/C++字符串。注意:也可以为以char数组,但必须以 \0 结尾,否则报错;
* @return
*/
void setStaticStringField(JNIEnv * env,jclass jclazz,const char* field_name, char* value){
//获取类属性ID
jfieldID fid = env->GetStaticFieldID(jclazz,field_name,"Ljava/lang/String;");
//设置类属性值
env->SetStaticObjectField(jclazz,fid,env->NewStringUTF(value));
}
3 JNI调用java实例或类方法
(1)涉及到的JNI接口
共涉及到92个JNI接口,按照功能看,其实只有5个接口,Type当做函数名的占位符,可以是Void、Short、Int、Long、Float、Double、Boolean、Byte、Char、Object。
接口 | 功能 |
GetMethodID | 获取用jmethod结构体类型表示的java实例方法ID |
GetStaticMethodID | 获取用jmethod结构体类型表示的java类方法ID |
Call<Type>Method Call<Type>MethodA Call<Type>MethodV | JNI调用java实例方法 |
CallStatic<Type>Method CallStatic<Type>MethodA CallStatic<Type>MethodV | JNI调用java类方法 |
CallNonvirtual<Type>Method CallNonvirtual<Type>MethodA CallNonvirtual<Type>MethodV | JNI调用java实例方法 或 调用被子类重写的父类实例方法 |
(2)接口详解
/**
* 作用:获取JNI定义的java实例方法ID
* @param clazz 方法所在的类的Class实例
* @param name 方法名字
* @param sig java方法签名
* @return 实例方法ID(JNI定义的jmethodID结构体实例)
*
* @exceptions 该方法可能抛出以下异常
* NoSuchMethodError 没找到指定实例方法
* ExceptionInInitializerError 初始化类时出错。即加载类时,执行static的属性、方法块时,出错了
* OutOfMemoryError 内存溢出
*/
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
/**
* 作用:获取JNI定义的java类方法ID
* @param clazz 方法所在的类的Class实例
* @param name 方法名字
* @param sig java方法签名
* @return 类方法ID
*
* @exceptions 该方法可能抛出以下异常
* NoSuchMethodError 没找到指定的类方法
* ExceptionInInitializerError 初始化类时出错。即加载类时,执行static的属性、方法块时,出错了
* OutOfMemoryError 内存溢出
*/
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);
/**
* 作用:JNI调用返回值为空的java实例方法
* @param obj 方法所属实例
* @param methodID JNI定义的java实例方法ID
* @param ... 可变个数 方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallVoidMethod(jobject obj, jmethodID methodID, ...);
jobject CallObjectMethod(jobject obj, jmethodID methodID, ...);
jboolean CallBooleanMethod (jobject obj, jmethodID methodID, ...);
jbyte CallByteMethod(jobject obj, jmethodID methodID, ...);
jchar CallCharMethod(jobject obj, jmethodID methodID, ...);
jshort CallShortMethod(jobject obj, jmethodID methodID, ...);
jint CallIntMethod(jobject obj, jmethodID methodID, ...);
jlong CallLongMethod(jobject obj, jmethodID methodID, ...);
jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...);
jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...);
/**
* 作用:JNI调用返回值为空的java实例方法
* @param obj 方法所属实例
* @param methodID JNI定义的java实例方法ID
* @param args jvalue* 类型的方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jobject CallObjectMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jbyte CallByteMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jchar CallCharMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jshort CallShortMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jint CallIntMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jlong CallLongMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jfloat CallFloatMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, const jvalue* args);
/**
* 作用:JNI调用返回值为空的java实例方法
* @param obj 方法所属实例
* @param methodID JNI定义的java实例方法ID
* @param args va_list表示的方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args);
jobject CallObjectMethodV(jobject obj, jmethodID methodID, va_list args);
jboolean CallBooleanMethodV(jobject obj, jmethodID methodID, va_list args);
jbyte CallByteMethodV(jobject obj, jmethodID methodID, va_list args);
jchar CallCharMethodV(jobject obj, jmethodID methodID, va_list args);
jshort CallShortMethodV(jobject obj, jmethodID methodID, va_list args);
jint CallIntMethodV(jobject obj, jmethodID methodID, va_list args);
jlong CallLongMethodV(jobject obj, jmethodID methodID, va_list args);
jfloat CallFloatMethodV(jobject obj, jmethodID methodID, va_list args);
jdouble CallDoubleMethodV(jobject obj, jmethodID methodID, va_list args);
/**
* 作用:JNI调用返回值为空的java类方法
* @param clazz 方法所属类
* @param methodID JNI定义的java类方法ID
* @param ... 可变个数 方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...);
jobject CallStaticObjectMethod(jobject obj, jmethodID methodID, ...);
jboolean CallStaticBooleanMethod (jobject obj, jmethodID methodID, ...);
jbyte CallStaticByteMethod(jobject obj, jmethodID methodID, ...);
jchar CallStaticCharMethod(jobject obj, jmethodID methodID, ...);
jshort CallStaticShortMethod(jobject obj, jmethodID methodID, ...);
jint CallStaticIntMethod(jobject obj, jmethodID methodID, ...);
jlong CallStaticLongMethod(jobject obj, jmethodID methodID, ...);
jfloat CallStaticFloatMethod(jobject obj, jmethodID methodID, ...);
jdouble CallStaticDoubleMethod(jobject obj, jmethodID methodID, ...);
/**
* 作用:JNI调用返回值为空的java类方法
* @param clazz 方法所属类
* @param methodID JNI定义的java类方法ID
* @param args jvalue*类型 方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args);
jobject CallStaticObjectMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jboolean CallStaticBooleanMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jbyte CallStaticByteMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jchar CallStaticCharMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jshort CallStaticShortMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jint CallStaticIntMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jlong CallStaticLongMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jfloat CallStaticFloatMethodA(jobject obj, jmethodID methodID, const jvalue* args);
jdouble CallStaticDoubleMethodA(jobject obj, jmethodID methodID, const jvalue* args);
/**
* 作用:JNI调用返回值为空的java类方法
* @param clazz 方法所属类
* @param methodID JNI定义的java类方法ID
* @param args va_list格式方法参数
*
* @exceptions 可能抛出任何异常
*/
void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args);
jobject CallStaticObjectMethodV(jobject obj, jmethodID methodID, va_list args);
jboolean CallStaticBooleanMethodV(jobject obj, jmethodID methodID, va_list args);
jbyte CallStaticByteMethodV(jobject obj, jmethodID methodID, va_list args);
jchar CallStaticCharMethodV(jobject obj, jmethodID methodID, va_list args);
jshort CallStaticShortMethodV(jobject obj, jmethodID methodID, va_list args);
jint CallStaticIntMethodV(jobject obj, jmethodID methodID, va_list args);
jlong CallStaticLongMethodV(jobject obj, jmethodID methodID, va_list args);
jfloat CallStaticFloatMethodV(jobject obj, jmethodID methodID, va_list args);
jdouble CallStaticDoubleMethodV(jobject obj, jmethodID methodID, va_list args);
/**
* 作用:JNI调用返回值为空的java实例方法 或 调用被子类重写的父类实例方法。
* @param obj 方法所属实例
* @param clazz 获取MethodID所用的clazz
* @param methodID JNI定义的java实例方法ID
* @param ... 方法参数
*
* @exceptions 可能抛出任何异常
*
* 注意:
* 1 clazz必须是获取MethodID所用的clazz。
* 2 可用于调用java实例方法,也可用于调用父类被重写的实例方法。
* 3 不可用于调用类方法
*
* 额外知识:
* 1 Non-Virtual 是C++中的概念,指的是父类不可重写的方法
*/
void CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jbyte CallNonvirtualByteMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jchar CallNonvirtualCharMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jshort CallNonvirtualShortMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jint CallNonvirtualIntMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jlong CallNonvirtualLongMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jfloat CallNonvirtualFloatMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
jdouble CallNonvirtualDoubleMethod(jobject obj, jclass clazz, jmethodID methodID, ...);
void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jobject CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jboolean CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jbyte CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jchar CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jshort CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jlong CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jfloat CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
jdouble CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args);
void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jobject CallNonvirtualObjectMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jboolean CallNonvirtualBooleanMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jbyte CallNonvirtualByteMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jchar CallNonvirtualCharMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jshort CallNonvirtualShortMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jint CallNonvirtualIntMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jlong CallNonvirtualLongMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jfloat CallNonvirtualFloatMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
jdouble CallNonvirtualDoubleMethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args);
(3)接口使用
以调用 返回值为java String类型(引用类型)且方法参数为... 的实例方法及类方法为例进行说明,调用返回值为java基本类型的方法与之基本相同。
/**
* 调用返回值为String类型的java实例方法
* @param env
* @param jobj 方法所属实例
* @param method_Name java方法名
* @param method_Symbol java方法符号
* @param ... java方法形参,个数可变,可以什么都不传
* @return
*/
jstring callStringMethod(JNIEnv * env,jobject jobj, const char* method_Name, const char* method_Symbol,...){
jstring result = nullptr;
jclass jcls = env->GetObjectClass(jobj);
//获取JNI定义的 jmethodID
jmethodID methodId = env->GetMethodID(jcls,method_Name,method_Symbol);
//通过va_list、va_start、va_end宏 操作 ... 参数
va_list args;
va_start(args, method_Symbol);
result = (jstring)(env->CallObjectMethodV(jobj, methodId, args));
va_end(args);
return result;
}
/**
* 调用返回值为String类型的java类方法
* @param env
* @param clazz java方法所属类
* @param method_Name java方法名
* @param method_Symbol java方法符号
* @param ... java方法形参,个数可变,可以什么都不传
* @return
*/
jstring callStaticStringMethod(JNIEnv * env, jclass clazz, const char* method_Name, const char* method_Symbol, ...){
jstring result = nullptr;
//获取JNI定义的 jmethodID
jmethodID methodId = env->GetStaticMethodID(clazz,method_Name,method_Symbol);
//通过va_list、va_start、va_end宏 操作 ... 参数
va_list args;
va_start(args, method_Symbol);
result = (jstring)(env->CallStaticObjectMethodV(clazz, methodId, args));
va_end(args);
return result;
}
/**
* 调用返回值为String类型的java实例方法
* @param env
* @param jobj 方法所属实例
* @param jcls 方法所属类
* @param method_Name java方法名
* @param method_Symbol java方法符号
* @param ...
* @return
*/
jstring callNonvirtualStringMethod(JNIEnv * env, jobject jobj, jclass jcls, const char* method_Name, const char* method_Symbol, ...){
jstring result = nullptr;
//获取JNI定义的 jmethodID
jmethodID methodId = env->GetMethodID(jcls,method_Name,method_Symbol);
//通过va_list、va_start、va_end宏 操作 ... 参数
va_list args;
va_start(args, method_Symbol);
//CallNonvirtualObjectMethodV所用的class实例必须与获取methodId所用的class实例保持一致
result = (jstring)(env->CallNonvirtualObjectMethodV(jobj,jcls,methodId,args));
va_end(args);
return result;
}
(4)相似接口区别
1)Call<Type>Method、Call<Type>MethodA、Call<Type>MethodV
三种方法的唯一区别就是最后一个形参的形式,该形参表示所调用的java方法的形参。
Call<Type>Method(jobject obj, jmethodID methodID, ...) | ... 表示任意个形参,传入时与java参数的位置和类型对应上即可,简单易用。 |
Call<Type>MethodA(jobject obj, jmethodID methodID, const jvalue* args) | jvalue是一个联合体(C++的一个类型),里面定义了9种类型的变量各一个,与所调用的java方法的形参类型对应上即可。缺点是不能定义两种相同类型的参数,实用性不强。 |
Call<Type>MethodV(jobject obj, jmethodID methodID, va_list args) | 与 ... 相似,直接给出了va_list。适用于需要对该接口二次封装的场景。 |
下面给出一个例子,看看三者使用上的区别,首先定义一个Father.java类:
public class Father {
private static final String TAG = Father.class.getSimpleName();
private String mName;
public String changeName(String name) {
mName = name;
Log.e(TAG, "name is " + mName);
return mName;
}
}
使用三种方法分别调用Father的实例方法getName(),如下:
//对CallObjectMethodV二次封装
jstring callStringMethod(JNIEnv * env,jobject jobj, const char* method_Name, const char* method_Symbol,...){
jstring result = nullptr;
jclass jcls = env->GetObjectClass(jobj);
//获取JNI定义的 jmethodID
jmethodID methodId = env->GetMethodID(jcls,method_Name,method_Symbol);
//通过va_list、va_start、va_end宏 操作 ... 参数
va_list args;
//将args指针指向 形参method_Symbol的下一个形参,即任意参数...的第一个值
va_start(args, method_Symbol);
result = (jstring)(env->CallObjectMethodV(jobj, methodId, args));
//va_list使用完后需要调用va_end宏
va_end(args);
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_boe_jnilearn_LearnJNI_useMethod(JNIEnv *env, jclass clazz, jobject father) {
jstring result = nullptr;
jclass jclz_father= env->GetObjectClass(father);
jmethodID methodId = env->GetMethodID(jclz_father,"changeName","(Ljava/lang/String;)Ljava/lang/String;");
///使用Call<Type>Method(jobject obj, jmethodID methodID, ...)
result = (jstring)env->CallObjectMethod(father,methodId,env->NewStringUTF("xiaoming"));
///使用Call<Type>MethodA(jobject obj, jmethodID methodID, const jvalue* args)
//初始化jvalue形式的形参
jvalue jval;
jvalue* pval = &jval;
pval->l = env->NewStringUTF("xiaoming");
result = (jstring)env->CallObjectMethodA(father,methodId,pval);
///使用Call<Type>MethodV(jobject obj, jmethodID methodID, va_list args)
result = callStringMethod(env,father,"changeName","(Ljava/lang/String;)Ljava/lang/String;",env->NewStringUTF("xiaoming"));
///打印java方法返回结果
const char* method_result = env->GetStringUTFChars(result, nullptr);
__android_log_write(ANDROID_LOG_ERROR, "cyy", method_result);
env->ReleaseStringUTFChars(result,method_result);
}
注意:... 表示任意个数参数,可以传多个值,也可以不传。
注意:va_list、va_start、va_end是三个宏,用于操作 ... ,具体的可见va_list使用方法_zldeng_scir的博客-CSDN博客_va_list
2)Call<Type>Method、CallNonvirtual<Type>Method
二者的主要区别就是能不能通过子类实例调用到父类被子类覆盖的方法。
接口 | 差异 |
Call<Type>Method (jobject obj, jmethodID methodID, ...) | 不能通过子类实例调用到父类被子类覆盖的方法,即使methodID是通过父类的class实例获取的,也不行 |
CallNonvirtual<Type>Method (jobject obj, jclass clazz, jmethodID methodID, ...) | 能通过子类实例调用到父类被子类覆盖的方法,但形参clazz,必须跟获取形参methodID所用的class实例一致 |
talk is cheap,我们搞个例子就清晰了,给Father.java类添加一个getName()方法,定义Child.java类,继承Father.java类,并重写Father类的getName()方法:
public class Father {
private static final String TAG = Father.class.getSimpleName();
private String mName = "李靖";
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String changeName(String name) {
mName = name;
Log.e(TAG, "name is " + mName);
return mName;
}
}
public class Child extends Father {
private static final String TAG = Child.class.getSimpleName();
private String mName = "哪吒";
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String changeName(String name) {
mName = name;
Log.e(TAG, "name is " + mName);
return mName;
}
}
在JNI层通过Child类实例,调用getName()方法:
extern "C"
JNIEXPORT void JNICALL
Java_com_boe_jnilearn_LearnJNI_useNonvirtualMethod(JNIEnv *env, jclass clazz, jobject child) {
///使用CallObjectMethod方法,通过Child实例调用getName()
//获取Child及Father类的Class实例
jclass cls_child = env->GetObjectClass(child);
jclass cls_father = env->GetSuperclass(cls_child);
//分别通过Child、Father类的Class实例获取getName()的方法ID
jmethodID mid_child_getName = env->GetMethodID(cls_child,"getName","()Ljava/lang/String;");
jmethodID mid_father_getName = env->GetMethodID(cls_father,"getName","()Ljava/lang/String;");
//CallObjectMethod 通过 mid_child_getName 调用getName()方法
jstring result1 = (jstring)env->CallObjectMethod(child,mid_child_getName);
//CallObjectMethod 通过 mid_father_getName 调用getName()方法
jstring result2 = (jstring)env->CallObjectMethod(child,mid_father_getName);
//CallNonvirtualObjectMethod 通过 mid_father_getName 调用getName()方法
jstring result3 = (jstring)env->CallNonvirtualObjectMethod(child,cls_father,mid_father_getName);
//打印1
const char* c_result1 = env->GetStringUTFChars(result1, nullptr);
__android_log_write(ANDROID_LOG_ERROR, "cyy", c_result1);
//打印2
const char* c_result2 = env->GetStringUTFChars(result2, nullptr);
__android_log_write(ANDROID_LOG_ERROR, "cyy", c_result2);
//打印3
const char* c_result3 = env->GetStringUTFChars(result3, nullptr);
__android_log_write(ANDROID_LOG_ERROR, "cyy", c_result3);
}
运行程序打印为:哪吒、哪吒、李静。
可见,CallObjectMethod方法不论是用哪个jmethodID实例调用getName()方法,调用的都是Child实例的getName()方法;而CallNonvirtualObjectMethod可使用根据Father类的class实例获取到的jmethodID,成功调用到Father类的getName()方法。
注意:CallNonvirtualObjectMethod方法传入的jclass和用于获取jmethodID的jclass必须一致,否则报错,导致程序崩溃。
注意:CallNonvirtualObjectMethod方法中的Nonvirtual表示,非虚函数,在C++中用virtual关键字形容的函数是虚函数,表示希望被重写的函数;而没被virtual关键字修饰的就是非虚函数,表示不希望被重写的方法;CallNonvirtualObjectMethod函数名字即可理解为调用没有被重写的java方法。
4 JNI创建java实例
(1)涉及到的JNI接口
共涉及到4个新的JNI接口,按照功能看,其实只有2个新接口。
接口 | 作用 |
NewObject NewObjectV NewObjectA | 实例化java实例 |
AllocObject | 为java实例分配内存,但并不初始化实例 |
注意:不初始化实例包括:不执行对实例属性的初始化赋值操作、不调用实例的构造方法。
(2)接口详解
/**
* 创建并初始化 clazz所代表java类的实例
* @param clazz java.lang.Class实例引用
* @param methodID JNI标准表示的java类构造方法ID
* @param 构造方法的参数 有三种形式 ... / va_list / const jvalue*
* @return clazz所代表java类的实例引用
*
* @exceptions
* InstantiationException 若clazz实例代表 接口 或 抽象类,则报该异常
* OutOfMemoryError 若系统空间不足,则报该异常
* java构造函数可能报的异常
*
* 注意:不能传入数组的Class实例
*/
jobject NewObject(jclass clazz, jmethodID methodID, ...);
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args);
/**
* 创建clazz所代表的类的实例,但并不初始化该实例
* @param clazz java.lang.Class实例引用
* @return 返回clazz所代表的类的实例引用
*
* @exceptions
* InstantiationException 若clazz实例代表 接口 或 抽象类,则报该异常
* OutOfMemoryError 若系统空间不足,则报该异常
* java构造函数可能报的异常
*
* 注意:不能传入数组的Class实例
*/
jobject AllocObject(jclass clazz);
注意:这两个方法不能传入java数组的Class实例。
(3)接口使用
使用上述两个接口,分别实例化java Child类 实例。
/**
* 创建指定java类的实例
* @param env
* @param jcls 指定java类的Class实例
* @param ...
* @return 指定java类的实例
*
* 注意:jcls 不能是数组的Class实例
*/
jobject createInstance(JNIEnv * env, jclass jcls, ...){
//获取java类构造方法
jmethodID mid_constructor = env->GetMethodID(jcls,"<init>","()V");
//通过va_start及va_end宏,获取 ... 形式的参数列表
va_list args;
va_start(args, jcls);
///通过 NewObject 方法创建java实例
jobject jobj1 = env->NewObject(jcls,mid_constructor);
///通过 AllocObject 方法创建java实例
//1 先为java实例分配存储空间
jobject jobj2 = env->AllocObject(jcls);
//2 再调用java实例的构造方法,初始化实例
env->CallVoidMethod(jobj2,mid_constructor,args);
va_end(args);
return jobj2;
}
可见,NewObject方法包括 为实例分配存储空间 和 初始化实例 两个操作;而 AllocObject 只负责为实例分配存储空间,初始化实例 需要调用CallVoidMethod函数完成,实现了分步 初始化java实例,这在java层实是无法实现的。
5 与java反射相关的JNI接口
(1)涉及到的JNI接口
JNI定义了四个接口,用于处理java反射相关的实例,简介如下:
接口 | 作用 |
FromReflectedMethod | 将 java.lang.reflect.Method 或者 java.lang.reflect.Constructor 实例转换为 jmethodID实例 |
ToReflectedMethod | 将 jmethodID实例转化为 java.lang.reflect.Method 或者 java.lang.reflect.Constructor 实例 |
FromReflectedField | 将 java.lang.reflect.Field 实例转化为 jfieldID实例 |
ToReflectedField | 将 jfieldID 实例转化为 java.lang.reflect.Field实例 |
(2)接口详解
/**
* 作用:将 java.lang.reflect.Method 或者 java.lang.reflect.Constructor 实例转换为 jmethodID实例
* @param method java反射包中的 java.lang.reflect.Method 或者 java.lang.reflect.Constructor实例
* @return 返回JNI定义的jmethodID结构体实例 或 NULL(抛出异常时)
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*/
jmethodID FromReflectedMethod(jobject method);
/**
* 作用:将 jmethodID实例转化为 java.lang.reflect.Method 或者 java.lang.reflect.Constructor 实例
* @param cls methodID对应的java方法所属的类的Class实例
* @param methodID java方法对应的 methodID
* @param isStatic 是否为静态方法,是 JNI_TRUE,否 JNI_FALSE。
* @return java.lang.reflect.Method 或者 java.lang.reflect.Constructor 实例 或 NULL(抛出异常时)
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*/
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic);
/**
* 作用:将 java.lang.reflect.Field 实例转化为 jfieldID实例
* @param method java反射包中的 java.lang.reflect.Field 实例
* @return 返回JNI定义的jfieldID结构体实例 或 NULL(抛出异常时)
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*/
jfieldID FromReflectedField(jobject field);
/**
* 作用:将 jfieldID 实例转化为 java.lang.reflect.Field实例
* @param cls fieldID 对应的java属性所属的类的Class实例
* @param methodID java属性对应的 fieldID
* @param isStatic 是否为静态属性,是 JNI_TRUE,否 JNI_FALSE。
* @return java.lang.reflect.Field 实例 或 NULL(抛出异常时)
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*/
jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic);
(3)接口使用
这里就不写例子了,感觉用的情况不多,弟经验不足,还想不到这几个接口必要的使用场景。我猜,FromReflectedMethod接口,可能可以避免在JNI中创建jmethod时,需要java 方法签名,而java 方法签名不是一下就能想到的,需要查下表。
6 其他与java实例/类相关的JNI接口
(1)涉及到的JNI接口
接口 | 作用 |
IsAssignableFrom | 用于判断两个java类是否可以强制类型转换 |
IsSameObject | 用于判断两个java对象是否相等 |
IsInstanceOf | 用于判断java实例是否为指定类的实例 |
(2)接口详解
/**
* 作用:用于判断clazz1代表的类/接口类型 能否类型转化 为clazz2代表的类/接口类型,
* 即判断clazz1代表的类/接口是否是clazz2代表的类/接口的子类或是否是同一个类。
* @param clazz1 java.lang.Class实例,不能是NULL
* @param clazz2 java.lang.Class实例 不能是NULL
* @return 返回值为JNI_TRUE或JNI_FALSE。
* 以下情况返回JNI_TRUE:
* 若clazz1和clazz2代表类/接口
* 1 clazz1和clazz2代表同一个类或接口。
* 2 clazz1代表的类/接口 继承了/实现了 clazz2代表的类/接口;
* 若clazz1和clazz2代表数组
* 1 clazz1代表的数组的元素类型为A,clazz2代表的数组的元素类型为B,IsAssignableFrom(clazz1, clazz2)返回JNI_TRUE。
* 其他情况返回JNI_FALSE。
*
* @exceptions 不报异常
*/
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2);
/**
* 作用:判断两个java 对象是否相等 也可用于判断JNI中的弱引用是否还存活(即所指向的实例是否被回收)
* @param ref1 java对象
* @param ref2 java对象
* @return 相等返回JNI_TRUE,不相等返回JNI_FALSE
*
* @exceptions 不报异常
*/
jboolean IsSameObject(jobject ref1, jobject ref2);
/**
* 作用:判断实例 obj,是否为clazz类型或者可以转化为clazz类型的实例
* @param obj
* @param clazz 不能是NULL
* @return 是返回JNI_TRUE,否JNI_FALSE
*
* @exceptions 不报异常
*/
jboolean IsInstanceOf(jobject obj, jclass clazz);
注意: IsSameObject 接口还可以用于判断JNI中的弱引用所指向的实例是否被回收,以便判断是否需要释放这个弱引用。
(3)接口使用
接口比较简单,使用就不介绍了
好了分享就到这里,如有问题请告知,希望大家点个赞支持一下!!!