Android JNI开发系列(九)JNI调用Java的静态方法&实例方法

好久不见. 提交于 2019-12-03 16:00:22

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 提供了一系列不同返回值的函数,如:CallStaticIntMethodCallStaticFloatMethodCallStaticShortMethodCallStaticObjectMethod等,分别表示调用返回值为 intfloatshortObject 类型的函数,引用类型统一调用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 实例后,要做异常判断
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!