人间观察
人只有不为生存而烦恼的时候,才会追求真正想要的东西。
在前面的几篇文章中有涉及到Java和JNI的通信,比如异常回调,Java和JNI的互相调用等。其中都免不了在通信过程中需要知道Java基本数据类型,引用类型和JNI的对应关系以及基本数据类型,引用类型的类型描述符,才能够通信和使用。
这个是很重要的,是基础,有必要单独来记录下。
在 JNI 开发中,Java 的数据类型并不是直接在 JNI 里使用的,例如 int 就在JNI中是使用 jint 来表示的。
数据类型对应
基本数据类型:
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。
示例
java 层
public native void data(byte b, char c, boolean bool, short s, int i, float f, double d, long l, float[] floats);
jni层
extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_data_JNIData_data(JNIEnv *env, jobject thiz, jbyte b, jchar c,
jboolean j_bool,
jshort s, jint i, jfloat f, jdouble d, jlong l,
jfloatArray floats) {
LOG_D("byte=%d", b);
LOG_D("jchar=%c", c);
LOG_D("jboolean=%d", j_bool);
LOG_D("jshort=%d", s);
LOG_D("jint=%d", i);
LOG_D("jfloat=%f", f);
LOG_D("jdouble=%lf", d);
LOG_D("jlong=%lld", l);
jfloat *float_p = env->GetFloatArrayElements(floats, nullptr);
jsize size = env->GetArrayLength(floats);
for (int index = 0; index < size; index++) {
LOG_D("floats[%d]=%lf", index, *(float_p++));
}
env->ReleaseFloatArrayElements(floats, float_p, 0);
}
输出日志
2020-10-30 15:00:24.819 2588-2588/com.bj.gxz.jniapp D/JNI: byte=100
2020-10-30 15:00:24.819 2588-2588/com.bj.gxz.jniapp D/JNI: jchar=A
2020-10-30 15:00:24.819 2588-2588/com.bj.gxz.jniapp D/JNI: jboolean=1
2020-10-30 15:00:24.819 2588-2588/com.bj.gxz.jniapp D/JNI: jshort=100
2020-10-30 15:00:24.819 2588-2588/com.bj.gxz.jniapp D/JNI: jint=100
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: jfloat=100.000000
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: jdouble=100.000000
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: jlong=100
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: floats[0]=1.000000
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: floats[1]=2.100000
2020-10-30 15:00:24.820 2588-2588/com.bj.gxz.jniapp D/JNI: floats[2]=3.300000
关于java复杂对象的传递可以参考上篇文章。
描述符/签名
我们平时定义的int,float,String等类型在JVM虚拟机中,存储数据类型的名称时是使用描述符来存储,它们有固定的规则/语法。如下表格:
Java类型 | 类型描述符/签名 |
---|---|
int | I |
long | J |
byte | B |
short | S |
char | C |
float | F |
double | D |
boolean | Z |
void | V |
数组 | [ |
二维数组 | [[ |
其他引用类型 | L+类全名+; |
如何查看描述符/签名
可以使用jdk提供的javap -s A.class
命令,-s输出内部类型签名。A.class为class的全路径。试下javap -s AppInfo.class
AppInfo.class
为AppInfo.java
编译后的.class
文。可以获取任何一个类的成员变量和方法的描述符/签名。
demo:
方法或者成员变量下面的descriptor就是对应它的描述符/签名。
B000000073160:methodfield guxiuzhong$ pwd
/Users/guxiuzhong/Desktop/JNIAPP/app/build/intermediates/javac/debug/classes/com/bj/gxz/jniapp/methodfield
B000000073160:methodfield guxiuzhong$ javap -s AppInfo.class
Compiled from "AppInfo.java"
public class com.bj.gxz.jniapp.methodfield.AppInfo implements java.io.Serializable {
public int versionCode;
descriptor: I
public long size;
descriptor: J
public com.bj.gxz.jniapp.methodfield.AppInfo(java.lang.String);
descriptor: (Ljava/lang/String;)V
public com.bj.gxz.jniapp.methodfield.AppInfo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
public java.lang.String getVersionName();
descriptor: ()Ljava/lang/String;
public void setVersionName(java.lang.String);
descriptor: (Ljava/lang/String;)V
public int getVersionCode();
descriptor: ()I
public void setVersionCode(int);
descriptor: (I)V
public void setSize(long);
descriptor: (J)V
public long getSize();
descriptor: ()J
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
}
B000000073160:methodfield guxiuzhong$
类描述符
类描述符的规则是:类全名(包名+类名)将原来的.
分隔符换成/
分隔符。比如
在java代码中的java.lang.String类的类描述符就是java/lang/String
java.io.InputStream类的类描述符就是java/io/InputStream
demo中的AppInfo就是com/bj/gxz/jniapp/methodfield/AppInfo
在jni中获取java的class就是通过类描述符获取,比如:
jclass cls = env->FindClass("com/bj/gxz/jniapp/methodfield/AppInfo");
成员变量描述符/成员变量签名
-
基本数据类型的描述符
基本数据类型就是如表格所示,与上面我们用javap出来的一样。
-
引用类型的描述符
当引用类型作为成员变量时,它的规则就是:
L类全名;
大写L
英文;
类全名(包名+类名)将原来的.
分隔符换成/
分隔符
比如
在java代码中的java.lang.String类的类描述符就是Ljava/lang/String;
java.io.InputStream类的类描述符就是Ljava/io/InputStream;
demo中的AppInfo就是Lcom/bj/gxz/jniapp/methodfield/AppInfo;
方法描述符/方法签名
规则为: (参数的域描述符的叠加)返回类型描述符,没有返回值的用V(void类型)
比如
java方法void method (int a)
对应的方法签名是 (I)V
java方法int method (byte[ ] bytes)
对应的方法签名是 ([B)I
再比如demo中的
public void setSize(long);
descriptor: (J)V
public void setVersionName(java.lang.String);
descriptor: (Ljava/lang/String;)V
这玩意要记吗?熟还能生巧,不用死记,最开始可以用javap -s A.class
,慢慢的就能写出来了。