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方法里java
# 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); }
注意:android
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 虚拟机实现方式不同的缘由。因此有申请就及时释放是一个好的习惯!(局部引用和全局引用在后面的文章中会详细介绍)函数
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"
命令查看方法签名spa
CallStaticXXXMethod/V/A
函数,XXX 表明返回值的数据类型。如:CallStaticIntMethod
CallXXXMethod/V/A
函数,XXX 表明返回的数据类型,如: `CallIntMethod
GetMethodID
函数,传入方法名称和方法签名GetStaticMethodID
函数,传入方法名称和方法签名FindClass
函数,传入类描述符。JVM 会从 classpath 目录下开始搜索。NewObject
函数,传入 Class 引用和构造方法 IDDeleteLocalRef
, 传入引用变量Ljava/lang/String;
GetMethodID
获取方法 ID 和调用 FindClass
获取 Class 实例后,要作异常判断