访问成员变量
无论是实例变量还是静态变量,分三个步骤:
- 获取类。表示需要访问哪一个java类,如所在jni方法对应java实例方法,参数中有jobject对象,则需要通过GetObjectClass得到类;如所在jni方法对应java静态方法,参数中已有jclass对象。
- 获取域ID。表示要访问类中的哪个变量。
- 获取域值。即获取变量的值。
示例:java类中有实例变量和静态变量
public class MainActivity extends AppCompatActivity {
private String instanceField = "instace";
private static String staticField = "static field";
...
}
jni中获取时,GetFieldID方法的第二个参数为变量的名称,第三个参数为变量的签名描述符(见下文)
extern "C" JNIEXPORT void JNICALL
Java_smarttime_tsia_com_jnitest3_MainActivity_stringFromJNI(JNIEnv* env, jobject obj) {
// 获取类
jclass clazz = env->GetObjectClass(obj);
// 获取实例变量ID
jfieldID instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String;");
// 获取实例变量值
jstring instanceValue = static_cast<jstring>(env->GetObjectField(obj, instanceFieldId));
// 获取静态变量ID
jfieldID staticField = env->GetStaticFieldID(clazz, "staticField", "Ljava/lang/String;");
// 获取静态变量值
jstring staticValue = static_cast<jstring>(env->GetStaticObjectField(clazz, staticField));
...
}
- 若域ID频繁使用,可以缓存下来以提高程序性能。
- 原生代码方位java变量需要调用两到三个jni函数,给程序增加了额外的负担,导致性能的下降。一般情况下,建议将所有需要的参数传递给原生方法调用。
访问成员方法
分三个步骤:
- 获取类。表示需要访问哪一个java类,如所在jni方法对应java实例方法,参数中有jobject对象,则需要通过GetObjectClass得到类;如所在jni方法对应java静态方法,参数中已有jclass对象。
- 获取方法ID。表示要访问类中的哪个方法
- 调用方法。
示例:java类中有实例方法和静态方法
public class MainActivity extends AppCompatActivity {
...
private String instanceMethod() {
return "instance method";
}
private static String staticMethod() {
return "static method";
}
...
现在jni中调用,并获得其返回值,GetMethodID第二个参数为方法名称,第三个参数为方法的签名描述符(见下文)
extern "C" JNIEXPORT void JNICALL
Java_smarttime_tsia_com_jnitest3_MainActivity_stringFromJNI3(JNIEnv* env, jobject obj) {
// 获取类
jclass clazz = env->GetObjectClass(obj);
// 获取实例方法ID
jmethodID instancemethodId = env->GetMethodID(clazz, "instanceMethod", "()Ljava/lang/String;");
// 调用实例方法
jstring instanceRet = static_cast<jstring>(env->CallObjectMethod(obj, instancemethodId));
// 获取静态方法ID
jmethodID staticmethodId = env->GetStaticMethodID(clazz, "staticMethod", "()Ljava/lang/String;");
// 调用静态方法
jstring staticRet = static_cast<jstring>(env->CallStaticObjectMethod(clazz, staticmethodId));
...
}
- 若方法ID频繁使用,可以缓存下来以提高程序性能。
- java和原生代码之间的转换代价较大,程序设计上要最小化在原生代码调用java方法,以提高程序的性能。
签名描述符
上文中使用到的签名描述符用来标识能标识变量的类型、方法的参数和返回值,获取描述符有两种方式:
命令生成方式
javap是JDK提供的命令行方式下java类文件的反编译程序,它可以从编译的类文件中解压缩变量和方法的描述符。
非private的变量或方法才会显示描述符,若为private方法可先改成public/protected然后生成,其描述符是一样的。
手动书写
java基本类型和描述符对应表:
Java Language Type | Field Desciptor |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
对于引用类型,描述符是以"L"开头,";"结尾
Java Language Type | Field Desciptor |
---|---|
String | Ljava/lang/String; |
Object[] | [Ljava/lang/Object; |
对于数组类型,使用"["和对应的类型描述符来表述,例
Java Langauage Type | Descriptor |
---|---|
int[][] | [[I |
double[][] | [[[D |
方法描述符一般格式为:(参数desciptor)返回值desciptor,示例:
Java Language Method | Method Descriptor |
---|---|
void fun(long v1, String v2, long v3) | (JLjava/lang/String;J)V |
String f(); | ()Ljava/lang/String; |
void fun(byte[] bytes); | ([B)V |
可以使用命令生成来验证手动写的是否正确。