Android-JNI开发系列《六》jni与java的交互
人间观察
1024-程序员节
愿各位程序员历尽千帆,归来仍是少年。
这片文章本来不打算写的,因为在前面的文章多多少少的提到了jni和java的交互,但是为了让知识体系更健全写,还是梳理下,算是jni和java的在交互上的一个总结吧。
两者的交互归纳起来主要就是两种。
- java调用jni。比如:传递基本数据,复杂对象等
- jni调用java。比如回调,异常,调用java方法/成员变量,构造java对象等等
java调用jni-传到复杂对象到jni中
我们新建一个java的对象,然后传递到jni中,在jni中获取该对象的属性值。
java对象如下
package com.bj.gxz.jniapp.methodfield;
import java.io.Serializable;
/**
* Created by guxiuzhong on 2020/10/24.
*/
public class AppInfo implements Serializable {
private static final String TAG = "AppInfo";
private String versionName;
public int versionCode;
public long size;
public AppInfo(String versionName) {
this.versionName = versionName;
}
public AppInfo(String versionName, int versionCode) {
this.versionName = versionName;
this.versionCode = versionCode;
}
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public int getVersionCode() {
return versionCode;
}
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
public void setSize(long size) {
this.size = size;
}
public long getSize() {
return size;
}
@Override
public String toString() {
return "AppInfo{" +
"versionName='" + versionName + '\'' +
", versionCode=" + versionCode +
", size=" + size +
'}';
}
}
jni接口为
public native void getAppInfoFromJava(AppInfo appInfo);
对应jni的方法是
extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_methodfield_JNIMethodField_getAppInfoFromJava(JNIEnv *env, jobject instance,
jobject obj) {
// ...
}
最后一个参数obj就是对应java传过来的对象AppInfo
。 因为在jni中所有的java对象都是jobject
。对应关系,这里再贴一下:
基本数据类型:
java与Native映射关系如下表所示:
Java类型 | Native 类型 | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint signed | 32 bits |
long jlong | signed | 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | not applicable |
引用数据类型
外面的为jni中的,括号中的java中的。
- jobject
- jclass (java.lang.Class objects)
- jstring (java.lang.String objects)
- jarray (arrays)
- jobjectArray (object arrays)
- jbooleanArray (boolean arrays)
- jbyteArray (byte arrays)
- jcharArray (char arrays)
- jshortArray (short arrays)
- jintArray (int arrays)
- jlongArray (long arrays)
- jfloatArray (float arrays)
- jdoubleArray (double arrays)
- jthrowable (java.lang.Throwable objects)
上面的层次中的jni的引用类型代表了继承关系,jbooleanArray继承jarray,jarray继承jobject,最终都继承jobject。
ok。
jni调用java对象的方法
调用对象的某个方法 Call<返回类型>Method<传参类型>,比如调用AppInfo
的getVersionCode
对应的就是CallIntMethod
,调用setVersionCode
对应的就是CallVoidMethod
方法,
Call<返回类型>Method<传参类型> | Native 类型 | java类型 |
---|---|---|
CallVoidMethod() CallVoidMethodA() CallVoidMethodV() | void | void |
CallObjectMethod() CallObjectMethodA() CallObjectMethodV() | jobject | object |
CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV() | jboolean | boolean |
CallByteMethod() CallByteMethodA() CallByteMethodV() | jbyte | byte |
CallCharMethod() CallCharMethodA() CallCharMethodV() | jchar | char |
CallShortMethod() CallShortMethodA() CallShortMethodV() | jshort | short |
CallIntMethod() CallIntMethodA() CallIntMethodV() | jint | int |
CallLongMethod() CallLongMethodA() CallLongMethodV() | jlong | long |
CallFloatMethod() CallFloatMethod A() CallFloatMethodV() | jlong | long |
CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV() | jfloat | float |
如果java方法是静态的如下 | - | - |
CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV() | jshort | short |
省略其它方法.... | - | - |
具体可以参考官网:官网开发文档
每一种返回类型对应3个方法,唯一不同的是输入参数的传入形式不同。所以getVersionName
对应的就是CallObjectMethod
。
CallObjectMethod参数:
obj:某个 Java 对象实例
methodID:指定方法的ID
args:输入参数列表,方法如果没有参数则不写
特别注意
如果你调用的是Java对象的方法CallxxxxMethod
第一个参数是某个 Java 对象实例。但是如果你调用的是静态方法,则第一个参数是jclass
。静态方法并不属于某一个对象。
methodID的获取通过GetMethodID
,在jni.h
头文件中函数声明原型为:
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
GetMethodID参数
clazz: 对应java的class类; 为java类的全类名比如把点改为/即com.bj.gxz.jniapp.methodfield.AppInfo
改为com/bj/gxz/jniapp/methodfield/AppInfo
const char* name 方法名字;
const char* sig 方法的签名,方法的签名可以通过javap -s xxx.class
获取。
示例获取getVersionName
的完整代码如下:
// 根据java对象获取对象对应的class
jclass cls = env->GetObjectClass(obj);
// 获取&调用java方法
jmethodID getVersionName_mid = env->GetMethodID(cls, "getVersionName", "()Ljava/lang/String;");
jstring versionName = (jstring) env->CallObjectMethod(obj, getVersionName_mid);
char *c_string = const_cast<char *>(env->GetStringUTFChars(versionName, 0));
LOG_D("versionName=%s", c_string);
看着很简单吧。
jni获取java对象的属性值
获取java对象的属性对应的值的方法为GetXXXXField
,XXXX 为数据类型,比如GetIntField
,GetShortField
等等,如果不是基本型则为GetObjectField
,如果属性为static
的则加上Static
,比如GetStaticIntField
,GetStaticObjectField
。在jni中是不看成员变量的作用域的,不管你是private
,protected
,public
的,加finnal
也一样,它都可以读取里面的值,和反射不一样。
GetXXXXField参数
obj:某个 Java 对象实例
jfieldID:指定属性的ID
GetFieldID参数
clazz:对象java对象的class
const char* name:属性名字
const char* sig:数据类型描述符
数据类型描述符java和jni的对应关系如下,来:
Java类型 | 类型描述符 |
---|---|
int | I |
long | J |
byte | B |
short | S |
char | C |
float | F |
double | D |
boolean | Z |
void | V |
数组 | [ |
二维数组 | [[ |
其他引用类型 | L+类全名+; |
例子如下:
// 获取java对象的属性值
jfieldID versionCode_fieldId = env->GetFieldID(cls, "versionCode", "I");
int versionCode = env->GetIntField(obj, versionCode_fieldId);
LOG_D("versionCode=%d", versionCode);
// 获取java对象的属性值
jfieldID size_fieldId = env->GetFieldID(cls, "size", "J");
long size = env->GetLongField(obj, size_fieldId);
LOG_D("size=%ld", size);
// 获取java静态属性的值
jfieldID tag_fieldId = env->GetStaticFieldID(cls, "TAG", "Ljava/lang/String;");
jstring tag_java = (jstring) env->GetStaticObjectField(cls, tag_fieldId);
char *tag_c_string = const_cast<char *>(env->GetStringUTFChars(tag_java, 0));
LOG_D("TAG=%s", tag_c_string);
运行后:
JNIMethodField jniMethodField = new JNIMethodField();
AppInfo javaInfo = new AppInfo("com.wg.com", 30);
javaInfo.setSize(500);
jniMethodField.getAppInfoFromJava(javaInfo);
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionName=com.wg.com
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionCode=30
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: size=500
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: TAG=AppInfo
属性的获取和方法的获取都差不多。
jni构造java对象&调用java方法
关于的回调异常可以参考前面的文章。
Android-JNI开发系列-在jni层的线程中回调到java层
我们在jni中创建一个java对象,其实就是调用java的构造方法,只不过是构造方法的函数签名为固定值<init>
. 在jni中创建复杂对象(任何java对象)用NewObject方法,同样有不同参数的NewObjectV
,NewObjectA
jni.h
函数声明原型为:
jobject NewObject(jclass clazz, jmethodID methodID, ...)
参数
clazz java类的class;
methodID 指定方法的ID;
... 参数 支持多个参数;
clazz和methodID同上文方法中的介绍。
示例如下:
// 获取java的class
jclass cls = env->FindClass("com/bj/gxz/jniapp/methodfield/AppInfo");
// 创建java对象,就是调用构造方法,构造方法的方法签名固定为<init>
jmethodID mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
jobject obj = env->NewObject(cls, mid, env->NewStringUTF("com.gxz.com"));
// 给定方法名字和签名,调用方法
jmethodID setVersionCode_mid = env->GetMethodID(cls, "setVersionCode", "(I)V");
env->CallVoidMethod(obj, setVersionCode_mid, 1);
// 给定属性名字和签名,设置属性的值
jfieldID size_field_id = env->GetFieldID(cls, "size", "J");
env->SetLongField(obj, size_field_id, (jlong) 1000);
size的属性我们是通过SetLongField设置的,见名知义。这个和获取属性一样/调用java中的方法一样,它也有类似SetLongField
,SetIntField
,SetStaticIntField
,SetObjectField
等等,区分了基本类型,静态属性,引用类型。大家可以尝试一下,这里不多介绍了。
运行后:
AppInfo info = jniMethodField.createAppInfoFromJni();
Log.e(TAG, "info=" + info);
输出
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp E/JNI: info=AppInfo{versionName='com.gxz.com', versionCode=1, size=1000}