Android Stuido Ndk-Jni 开发(五):Jni回调java静态方法和非静态方法
public native String getStringFromJni();
public void printHelloWorld(){
Log.d(TAG, "hello world! by unstatic method");
}
public static void printStaticHelloWorld(){
Log.d(TAG, "hello world! by static method");
}
1. jni回调静态方法
JNIEXPORT jstring JNICALL Java_com_dj_jni_jnimodule_NativeHelper_getStringFromJni
(JNIEnv *env, jobject obj){
jclass cls = (*env)->FindClass(env, "com/dj/jni/jnimodule/NativeHelper");
// jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID callback = (*env)->GetStaticMethodID(env, cls, "printStaticHelloWorld", "()V");
(*env)->CallStaticVoidMethod(env, cls, callback);
return (*env)->NewStringUTF(env,"Hello from JNI !");
}
参数JNIEnv 和 jobject的解释:
参数JNIEnv* env实际上代表了Java环境,通过JNIEnv* 这个指针就可以对Java端的代码进行操作。
例如调用java对象的方法、获取java对象的属性、创建java对象等等。jobject obj的含义取决于该native方法是不是static,比如
public native String getStringFromJni();
就不是static
如果native方法是static,obj表示这个natvie方法所在类的class对象。因为static方法不需要类实例。
如果native方法不是static,obj表示这个native方法所在的类的实例。
注意:
回调static方法,不需要用到obj。只需要通过(*env)->FindClass(env, "class name");
获取类class对象。也可以通过(*env)->GetObjectClass(env, obj)
获得类class对象。
然后通过GetStaticMethodID(env, clazz, name, sig)
和 CallStaticVoidMethod(env, clazz, methodID, args)
调用static方法。
2. jni回调非静态方法
JNIEXPORT jstring JNICALL Java_com_dj_jni_jnimodule_NativeHelper_getStringFromJni
(JNIEnv *env, jobject obj){
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID callback = (*env)->GetMethodID(env, cls, "printHelloWorld", "()V");
(*env)->CallVoidMethod(env, obj, callback);
return (*env)->NewStringUTF(env,"Hello from JNI !");
}
回调非静态方法通过GetMethodID(env, clazz, name, sig)
和 CallVoidMethod (env,obj, methodID, args)
调用。
注意:
CallVoidMethod(env, obj, methodID, args)
的第二个参数obj是类的实例。需要传入类实例化后的对象。
其对应的java代码的方式是:
NativeHelper nativeHelper = new NativeHelper();
nativeHelper.printHelloWorld();
CallStaticVoidMethod(env, clazz, methodID, args)
的第二个参数clazz是类的class实例。需要传入类class对象。
JNIEnv可以通过以下几种方式获取类class对象,即jclass:
jclass FindClass(const char* clsName) 根据类名来查找一个类,完整类名。
jclass GetObjectClass(jobject obj) 根据一个对象,获取该对象的类
jclass GetSuperClass(jclass obj) 获取一个类的父类
其对应的java代码的方式是:
NativeHelper.printStaticHelloWorld();
注意无论GetStaticMethodID(env, clazz, name, sig)
和GetMethodID(env, clazz, name, sig)
,其第二个参数clazz都是类的class实例。
转载请注明出处:http://blog.csdn.net/xyang81/article/details/42582213
通过前面5章的学习,我们知道了如何通过JNI函数来访问JVM中的基本数据类型、字符串和数组这些数据类型。下一步我们来学习本地代码如何与JVM中任意对象的属性和方法进行交互。比如本地代码调用Java层某个对象的方法或属性,也就是通常我们所说的来自C/C++层本地函数的callback(回调)。这个知识点分2篇文章分别介绍,本篇先介绍方法回调,在第七章中介绍本地代码访问Java的属性。
在这之前,先回顾一下在Java中调用一个方法时在JVM中的实现原理,有助于下面讲解本地代码调用Java方法实现的机制。写过Java的童鞋都知道,调用一个类的静态方法,直接通过 类名.方法 就可以调用。这也太简单了,有什么好讲的呢。。。但在这个调用过程中,JVM是帮我们做了很多工作的。当我们在运行一个Java程序时,JVM会先将程序运行时所要用到所有相关的class文件加载到JVM中,并采用按需加载的方式加载,也就是说某个类只有在被用到的时候才会被加载,这样设计的目的也是为了提高程序的性能和节约内存。所以我们在用类名调用一个静态方法之前,JVM首先会判断该类是否已经加载,如果没有被ClassLoader加载到JVM中,JVM会从classpath路径下查找该类,如果找到了,会将其加载到JVM中,然后才是调用该类的静态方法。如果没有找到,JVM会抛出java.lang.ClassNotFoundException异常,提示找不到这个类。ClassLoader是JVM加载class字节码文件的一种机制,不太了解的童鞋,请移步阅读《深入分析Java ClassLoader原理》一文。其实在JNI开发当中,本地代码也是按照上面的流程来访问类的静态方法或实例方法的,下面通过一个例子,详细介绍本地代码调用Java方法流程当中的每个步聚:
- package com.study.jnilearn;
- /**
- * AccessMethod.java
- * 本地代码访问类的实例方法和静态方法
- * @author yangxin
- */
- public class AccessMethod {
- public static native void callJavaStaticMethod();
- public static native void callJavaInstaceMethod();
- public static void main(String[] args) {
- callJavaStaticMethod();
- callJavaInstaceMethod();
- }
- static {
- System.loadLibrary("AccessMethod");
- }
- }
- package com.study.jnilearn;
- /**
- * ClassMethod.java
- * 用于本地代码调用
- * @author yangxin
- */
- public class ClassMethod {
- private static void callStaticMethod(String str, int i) {
- System.out.format("ClassMethod::callStaticMethod called!-->str=%s," +
- " i=%d\n", str, i);
- }
- private void callInstanceMethod(String str, int i) {
- System.out.format("ClassMethod::callInstanceMethod called!-->str=%s, " +
- "i=%d\n", str, i);
- }
- }
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_study_jnilearn_AccessMethod */
- #ifndef _Included_com_study_jnilearn_AccessMethod
- #define _Included_com_study_jnilearn_AccessMethod
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_study_jnilearn_AccessMethod
- * Method: callJavaStaticMethod
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
- (JNIEnv *, jclass);
- /*
- * Class: com_study_jnilearn_AccessMethod
- * Method: callJavaInstaceMethod
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
- (JNIEnv *, jclass);
- #ifdef __cplusplus
- }
- #endif
- #endif
- // AccessMethod.c
- #include "com_study_jnilearn_AccessMethod.h"
- /*
- * Class: com_study_jnilearn_AccessMethod
- * Method: callJavaStaticMethod
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
- (JNIEnv *env, jclass cls)
- {
- jclass clazz = NULL;
- jstring str_arg = NULL;
- jmethodID mid_static_method;
- // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
- clazz =(*env)->FindClass(env,"com/study/jnilearn/ClassMethod");
- if (clazz == NULL) {
- return;
- }
- // 2、从clazz类中查找callStaticMethod方法
- mid_static_method = (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V");
- if (mid_static_method == NULL) {
- printf("找不到callStaticMethod这个静态方法。");
- return;
- }
- // 3、调用clazz类的callStaticMethod静态方法
- str_arg = (*env)->NewStringUTF(env,"我是静态方法");
- (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);
- // 删除局部引用
- (*env)->DeleteLocalRef(env,clazz);
- (*env)->DeleteLocalRef(env,str_arg);
- }
- /*
- * Class: com_study_jnilearn_AccessMethod
- * Method: callJavaInstaceMethod
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
- (JNIEnv *env, jclass cls)
- {
- jclass clazz = NULL;
- jobject jobj = NULL;
- jmethodID mid_construct = NULL;
- jmethodID mid_instance = NULL;
- jstring str_arg = NULL;
- // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
- clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod");
- if (clazz == NULL) {
- printf("找不到'com.study.jnilearn.ClassMethod'这个类");
- return;
- }
- // 2、获取类的默认构造方法ID
- mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V");
- if (mid_construct == NULL) {
- printf("找不到默认的构造方法");
- return;
- }
- // 3、查找实例方法的ID
- mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");
- if (mid_instance == NULL) {
- return;
- }
- // 4、创建该类的实例
- jobj = (*env)->NewObject(env,clazz,mid_construct);
- if (jobj == NULL) {
- printf("在com.study.jnilearn.ClassMethod类中找不到callInstanceMethod方法");
- return;
- }
- // 5、调用对象的实例方法
- str_arg = (*env)->NewStringUTF(env,"我是实例方法");
- (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);
- // 删除局部引用
- (*env)->DeleteLocalRef(env,clazz);
- (*env)->DeleteLocalRef(env,jobj);
- (*env)->DeleteLocalRef(env,str_arg);
- }
代码解析:
AccessMethod.java是程序的入口,在main方法中,分别调用了callJavaStaticMethod和callJavaInstaceMethod这两个native方法,用于测试native层调用MethodClass.java中的callStaticMethod静态方法和callInstanceMethod实例方法,这两个方法的返回值都为Void,参数都有两个,分别为String和int
一、callJavaStaticMethod静态方法实现说明
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod
- (JNIEnv *env, jclass cls)
定位到AccessMethod.c的31行:
- (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);
CallStaticVoidMethod函数的原型如下:
- void (JNICALL *CallStaticVoidMethod)(JNIEnv *env, jclass cls, jmethodID methodID, ...);
env:JNI函数表指针
cls:调用该静态方法的Class对象
methodID:方法ID(因为一个类中会存在多个方法,需要一个唯一标识来确定调用类中的哪个方法)
参数4:方法实参列表
根据函数参数的提示,分以下四步完成Java静态方法的回调:
第一步:调用FindClass函数,传入一个Class描述符,JVM会从classpath路径下搜索该类,并返回jclass类型(用于存储Class对象的引用)。注意ClassMethod的Class描述符为com/study/jnilearn/ClassMethod,要将.(点)全部换成/(反斜杠)
- (*env)->FindClass(env,"com/study/jnilearn/ClassMethod");
- (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V");
第三步:调用CallStaticVoidMethod函数,执行ClassMethod.callStaticMethod方法调用。str_arg和100是callStaticMethod方法的实参。
- str_arg = (*env)->NewStringUTF(env,"我是静态方法");
- (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);
注意: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);
- // 删除局部引用
- (*env)->DeleteLocalRef(env,clazz);
- (*env)->DeleteLocalRef(env,str_arg);
二、callInstanceMethod实例方法实现说明
- JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
- (JNIEnv *env, jclass cls)
- (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);
- void (JNICALL *CallVoidMethod) (JNIEnv *env, jobject obj, jmethodID methodID, ...);
该函数接收4个参数:
env:JNI函数表指针
obj:调用该方法的实例
methodID:方法ID
参数4:方法的实参列表
第一步、同调用静态方法一样,首先通过FindClass函数获取类的Class对象
第二步、获取类的构造方法ID,因为创建类的对象首先会调用类的构造方法。这里以默认构造方法为例
- (*env)->GetMethodID(env,clazz, "<init>","()V");
第三步、调用GetMethodID获取callInstanceMethod的方法ID
- (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");
- <span style="font-size:18px;">(*env)->NewObject(env,clazz,mid_construct);</span>
- str_arg = (*env)->NewStringUTF(env,"我是实例方法");
- (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);
- void (JNICALL *CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
- void (JNICALL *CallVoidMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
- void (JNICALL *CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args);
- // 删除局部引用
- (*env)->DeleteLocalRef(env,clazz);
- (*env)->DeleteLocalRef(env,jobj);
- (*env)->DeleteLocalRef(env,str_arg);
在上面的的例子中,无论是调用静态方法还是实例方法,都必须传入一个jmethodID的参数。因为在Java中存在方法重载(方法名相同,参数列表不同),所以要明确告诉JVM调用的是类或实例中的哪一个方法。调用JNI的GetMethodID函数获取一个jmethodID时,需要传入一个方法名称和方法签名,方法名称就是在Java中定义的方法名,方法签名的格式为:(形参参数类型列表)返回值。形参参数列表中,引用类型以L开头,后面紧跟类的全路径名(需将.全部替换成/),以分号结尾。下面是一些示例:
Java基本类型与方法签名中参数类型和返回值类型的映射关系如下:
比如,String fun(int a, float b, boolean c, String d) 对应的JNI方法签名为:"(IFZLjava/lang/String;)Ljava/lang/String;"
总结:
1、调用静态方法使用CallStaticXXXMethod/V/A函数,XXX代表返回值的数据类型。如:CallStaticIntMethod
2、调用实例方法使用CallXXXMethod/V/A函数,XXX代表返回的数据类型,如:CallIntMethod
3、获取一个实例方法的ID,使用GetMethodID函数,传入方法名称和方法签名
4、获以一个静态方法的ID,使用GetStaticMethodID函数,传入方法名称和方法签名
5、获取构造方法ID,方法名称使用"<init>"
6、获取一个类的Class实例,使用FindClass函数,传入类描述符。JVM会从classpath目录下开始搜索。
7、创建一个类的实例,使用NewObject函数,传入Class引用和构造方法ID
8、删除局部变量引用,使用DeleteLocalRef,传入引用变量
9、方法签名格式:(形参参数列表)返回值类型。注意:形参参数列表之间不需要用空格或其它字符分隔
10、类描述符格式:L包名路径/类名;,包名之间用/分隔。如:Ljava/lang/String;
11、调用GetMethodID获取方法ID和调用FindClass获取Class实例后,要做异常判断
示例代码下载地址:https://code.csdn.net/xyang81/jnilearn