JNI调用Java的静态方法&实例方法
package org.professor.jni.bean;
import android.util.Log;
/**
* Created by peng on 2018/10/11.
*/
public class Person {
/*C/CPP 调用Java 静态方法 */
public native void callJavaStaticMethod();
/*C/C++ 调用Java 实例方法 */
public native void callJavaInstanceMethod();
public static void setPersonInfo(String name, int age) { Log.i("PERSON", "name= " + name + "\\t age=" + age); }
public void setPersonMoney(float money) { Log.i("PERSON", "money= " + money);
}
}
上面写了一个Java Bean类,里面定义了两个Native方法,分别用来调用,该类的静态方法和实例方法,实现在本地native方法里
JNI调用静态方法
# include
# include
# include
//extern "C" // C 编译器编译我的代码
JNIEXPORT void JNICALL Java_org_professor_jni_bean_Person_callJavaStaticMethod(JNIEnv *env, jobject instance) {
//1.获取类类型的Class对象
jclass personClass = (*env)->FindClass(env, "org/professor/jni/Person");
if (NULL == personClass) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND CLASS");
return;
}
//2.获取方法ID 方法签名:(String:Ljava/lang/String ;int:I) 方法签名可以通过javap -s命令生成
jmethodID pJmethodID = (*env)->GetStaticMethodID(env, personClass, "setPersonInfo",
"(Ljava/lang/String;I)V");
if (NULL == pJmethodID) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND STATIC METHOD");
return;
}
jstring str = (*env)->NewStringUTF(env, "Yimi");
//3.调用方法
(*env)->CallStaticVoidMethod(env, personClass, pJmethodID, str, 18);
// 删除局部变量引用表,JVM内部由于引用表,
// 记录全局和局部的变量,当超过引用表数量,导致内存溢出
// 造成崩溃
(*env)->DeleteLocalRef(env, personClass);
(*env)->DeleteLocalRef(env, str);
}
注意:
-
JVM 针对所有数据类型的返回值都定义了相关的函数。上面
callStaticMethod
方法的返回类型为void
,所以调用CallStaticVoidMethod
。根据返回值类型不同,JNI 提供了一系列不同返回值的函数,如:CallStaticIntMethod
、CallStaticFloatMethod
、CallStaticShortMethod
、CallStaticObjectMethod
等,分别表示调用返回值为int
、float
、short
、Object
类型的函数,引用类型统一调用CallStaticObjectMethod
函数。另外,每种返回值类型的函数都提供了接收3种实参类型的实现:CallStaticXXXMethod(env, clazz, methodID, ...)
,CallStaticXXXMethodV(env, clazz, methodID, va_list args)
,CallStaticXXXMethodA(env, clazz, methodID, const jvalue args)
,分别表示:接收可变参数列表、接收va_list
作为实参和接收const jvalue
为实参。下面是jni.h
头文件中CallStaticVoidMethod
的三种实参的函数原型:void (JNICALL *CallStaticVoidMethod) (JNIEnv *env, jclass cls, jmethodID methodID, ...); void (JNICALL *CallStaticVoidMethodV) (JNIEnv *env, jclass cls, jmethodID methodID, va_list args); void (JNICALL *CallStaticVoidMethodA) (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args);
-
虽然函数结束后,JVM 会自动释放所有局部引用变量所占的内存空间。但还是手动释放一下比较安全,因为在 JVM 中维护着一个引用表,用于存储局部和全局引用变量,经测试在 Android NDK 环境下,这个表的最大存储空间是512 个引用,如果超过这个数就会造成引用表溢出,JVM 崩溃。在 PC 环境下测试,不管申请多少局部引用也不释放都不会崩,我猜可能与 JVM 和 Android Dalvik 虚拟机实现方式不一样的原因。所以有申请就及时释放是一个好的习惯!(局部引用和全局引用在后面的文章中会详细介绍)
JNI调用实例方法
JNIEXPORT void JNICALL Java_org_professor_jni_bean_Person_callJavaInstanceMethod(JNIEnv *env, jobject instance) {
//1.获取类类型的Class对象
jclass personClass = (*env)->FindClass(env, "org/professor/jni/Person");
if (NULL == personClass) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND CLASS");
return;
}
//2.获取方法ID 方法签名:(String:Ljava/lang/String ;int:I)
jmethodID pJmethodID = (*env)->GetMethodID(env, personClass, "setPersonMoney",
"(F)V");
if (NULL == pJmethodID) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND INSTANCE METHOD");
return;
}
//3.获取默认的构造方法ID (<init()>; = public org.professor.jni.bean.Person(); ),先获取构造函数的ID
jmethodID constructor = (*env)->GetMethodID(env, personClass, "<init>", "()V");
if (NULL == constructor) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND INSTANCE CONSTRUCTOR METHOD");
return;
}
//4.获取默认构造函数对象
jobject object = (*env)->NewObject(env, personClass, constructor);
if (NULL == object) {
__android_log_print(ANDROID_LOG_ERROR, "PERSON", "NOT FIND OBJECT");
return;
}
//5.调用实例方法
(*env)->CallVoidMethod(env, object, pJmethodID, 30.0);
//6.删除局部引用变量
(*env)->DeleteLocalRef(env, personClass);
(*env)->DeleteLocalRef(env, object);
}
扩展方法签名表
签名 | java类型 |
---|---|
V | void |
Z | boolean |
I | int |
J | long |
D | double |
F | float |
B | byte |
C | char |
S | short |
[I | int[] |
[F | float[] |
[B | byte[] |
[C | char[] |
[S | short[] |
[D | double[] |
[J | long[] |
[Z | boolean[] |
L用/分割包的完整类名; Ljava/lang/String; | Object |
例如: void set(String str); 签名:"(Ljava/lang/String;)V"
可以用javap -s "ClassFile"
命令查看方法签名
总结
- 调用静态方法使用
CallStaticXXXMethod/V/A
函数,XXX 代表返回值的数据类型。如:CallStaticIntMethod
- 调用实例方法使用
CallXXXMethod/V/A
函数,XXX 代表返回的数据类型,如:`CallIntMethod
- 获取一个实例方法的 ID,使用
GetMethodID
函数,传入方法名称和方法签名 - 获以一个静态方法的 ID,使用
GetStaticMethodID
函数,传入方法名称和方法签名 - 获取构造方法 ID,方法名称使用""
- 获取一个类的 Class 实例,使用
FindClass
函数,传入类描述符。JVM 会从 classpath 目录下开始搜索。 - 创建一个类的实例,使用
NewObject
函数,传入 Class 引用和构造方法 ID - 删除局部变量引用,使用
DeleteLocalRef
, 传入引用变量 - 方法签名格式:(形参参数列表)返回值类型。注意:形参参数列表之间不需要用空格或其它字符分隔
- 类描述符格式:L 包名路径/类名;,包名之间用/分隔。如:
Ljava/lang/String;
- 调用
GetMethodID
获取方法 ID 和调用FindClass
获取 Class 实例后,要做异常判断