一、基本类型
Java 类型 | 本地类型 | 描述 | C 类型 |
---|---|---|---|
int | jint | signed 32 bits | 根据平台不同 |
long | jlong | signed 64 bits | 根据平台不同 |
byte | jbyte | signed 8 bits | 根据平台不同 |
char | jchar | unsigned 16 bits | typedef unsigned short |
short | jshort | singed 16 bits | typedef short |
boolean | jboolean | unsigned 8 bits | typedef unsigned char |
float | jfloat | 32 bits | typedef float |
double | jdouble | 64 bits | typedef double |
void | void | N/A | N/A |
二、引用类型性
JNI 中的引用类型主要包括:
- 类;
- 对象;
- 数组。
和 Java 中的引用类型的对应关系如下表所示:
Java 类型 | JNI 类型 | 描述 |
---|---|---|
Object | jobject | Object 类型 |
Class | jclass | Class 类型 |
String | jstring | String 类型 |
Object[] | jobjectArray | 对象数组 |
boolean[] | jbooleanArray | boolean 数组 |
byte[] | jbyteArray | byte 数组 |
char[] | jcharArray | char 数组 |
short[] | jshortArray | short 数组 |
int[] | jintArray | int 数组 |
long[] | jlongArray | long 数组 |
float[] | jfloatArray | float 数组 |
double[] | jdoubleArray | double 数组 |
Throwable | jthrowable | Throwable |
三、native 函数参数
native 函数参数说明:
每个 native 函数,都至少有两个参数:
JNIEnv*;
jclass 或 jobject:
- 当 native 方法为静态方法时:jclass 代表 native 方法所属类的 class 对象。
- 当 native 方法为非静态方法时:jobject 代表 native 方法所属的对象。
四、属性与方法签名
数据类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
reference | LClassname; |
type[] | [type |
method | type(arg-types)ret-type |
熟悉 JVM 的一定会发现这就是 JVM 中的字段描述符和方法描述符。
需要注意的是:
- 类描述符开头的 'L' 与结尾的 ';' 必须要有;
- 数组描述符,开头的 '[' 必须要有;
- 方法描述符规则: "(各参数描述符)返回值描述符",其中参数描述符间没有任何分隔符号。
五、C/C++ 访问 Java 的属性、方法
为了能够在 C/C++ 中调用 Java 中的类,jni.h 的头文件专门定义了 jclass 类型表示 Java 中 Class 类。JNIEnv 中有 3 个
函数可以获取 jclass。
jclass FindClass(const char* clsName)
通过类的名称(类的全名,这时候包名不是用"."点号而是用"/"来区分的)来获取 jclass。jclass GetObjectClass(jobject obj)
通过对象实例来获取 jclass,相当于 Java 中的 getClass() 函数jclass getSuperClass(jclass obj)
通过 jclass 可以获取其父类的 jclass 对象
在 JNI 调用中,肯定会涉及到本地方法操作 Java 类中数据和方法。JNI 要求程序员通过特殊的 JNI 函数来获取和设置数据以及调用 java 方法。
有以下几种情况:
- 访问 Java 类的非静态属性;
- 访问 Java 类的静态属性;
- 访问 Java 类的非静态方法;
- 访问 Java 类的静态方法;
- 间接访问 Java 类的父类的方法;
- 访问 Java 类的构造方法。
在 Native 本地代码中访问 Java 层的代码,一个常用的常见的场景就是获取 Java 类的属性和方法。所以为了在 C/C++ 获取 Java 层的属性和方法,JNI 在 jni.h 头文件中定义了 jfieldID 和 jmethodID 这两种类型来分别代表 Java 端的属性和方法。
在访问或者设置 Java 某个属性的时候,首先就要现在本地代码中取得代表该 Java 类的属性的 jfieldID,然后才能在本地代码中进行 Java 属性的操作,同样,在需要调用 Java 类的某个方法时,也是需要取得代表该方法的 jmethodID 才能进行 Java 方法操作。
5.1、访问 Java 的非静态属性
Java 声明如下:
public String name = "Jagger";
// 访问非静态属性 name,修改它的值
// accessField 自定义的一个方法
public native void accessField();
C 代码如下:
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_accessField
(JNIEnv *env, jobject jobject) {
// Jclass
jclass cls = (*env)->GetObjectClass(env, jobject);
// jfieldID 属性名称,属性签名
jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
// 获取属性值
jstring jstr = (*env)->GetObjectField(env, jobject, fid);
...
}
5.2、访问 Java 的静态属性
Java 声明如下:
public static int count = 13;
public native void accessStaticField();
C 代码如下:
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessStaticField
(JNIEnv *env, jobject jobj) {
// jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// jfieldID
jfieldID fid =(*env)->GetStaticFieldID(env, cls, "count", "I");
// GetStaticField
jint count = (*env)->GetStaticIntField(env, cls, fid);
...
}
常见的调用 Java 层的方法一般是使用 JNIEnv 来进行操作:
GetFieldID/GetMethodID:获取某个属性/某个方法;
GetStaticFieldID/GetStaticMethodID:获取某个静态属性/静态方法。
方法的具体实现如下:
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
以上四个函数都是 4 个入参,而且每个入参的都是:
- *JNIEnv *env;
- jclass clazz;
- const char *name;
- const char *sig。
JNIEnv 代表一个 JNI 环境接口,jclass 上面也说了代表 Java 层中的“类”,name 则代表方法名或者属性名。char *sig 代表代
表了 JNI 中的一个特殊字段 —— 签名。
5.3、访问 Java 的非静态方法
Java 声明如下:
public int genRandomInt(int max){
System.out.println("genRandomInt 执行了..");
return new Random().nextInt(max);
}
C 代码如下:
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessMethod
(JNIEnv *env, jobject jobj) {
//Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// JmethodID
jfieldID mFid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
// 调用
// CallMethod
jint random = (*env)->CallIntMethod(env, jobj, mFid, 200);
...
}
5.4、访问 Java 的静态方法
Java 声明如下:
public static String getUUID(){
return UUID.randomUUID().toString();
}
C 代码如下:
// 访问 Java 静态方法
JNIEXPORT void JNICALL Java_com_haocai_jni_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj) {
// Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
// JmethodID
jfieldID mFid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
// CallStaticMethod
jstring uuid = (*env)->CallStaticObjectMethod(env, jobj, mFid);
...
}
5.5、访问 Java 类的构造方法
Java 声明如下:
public native long accessConstructor();
C 代码如下:
JNIEXPORT jlong JNICALL Java_com_haocai_jni_JniTest_accesssConstructor
(JNIEnv *env, jobject jobj) {
// jclass
jclass cls = (*env)->FindClass(env, "java/util/Date");
// jmethodID
jmethodID constructor_mid= (*env)->GetMethodID(env, cls,"","()V");
// 实例化一个 Date 对象(可以在 constructor_mid 后加参)
jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
// 调用 getTime 方法
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
jlong time = (*env)->CallLongMethod(env, date_obj, mid);
printf("time:%lld\n",time);
return time;
}
这里还有另一种方法来调用构造函数,方法如下:
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
这里多了一个参数,即 jvalue *args,这里是 args 代表的是对应构造函数的所有参数的,我们可以应将传递给构造函数的所有参数放在 jvalues 类型的数组 args 中,该数组紧跟着放在 methodID 参数的后面。
参考
Android JNI学习(三)——Java与Native相互调用
Android NDK开发之旅11--JNI--JNI数据类型与方法属性访问