NDK/Jni Develop Guild(5)c/c++ access Java Method and static method

来源:互联网 发布:淘宝加盟店 编辑:程序博客网 时间:2024/05/19 12:28

NDK/Jni Develop Guild(5)c/c++ access Java Method and static method

  • 通过前面的学习,我们知道了如何通过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字节码文件的一种机制]其实在JNI开发当中,本地代码也是按照上面的流程来访问类的静态方法或实例方法的.
1:本地代码调用Java方法流程

下面我们在java 中定义两个native方法并且调用这两个方法

/** * Created by blueZhang on 16/6/28. * * @Author: BlueZhang * @date: 16/6/28 */public class IntArray {    public native int sumArray(int[] arr);    public native int[][] initInt2DArray(int size);    public static native String callJavaStaticMethod();    public static native String  callJavaInstaceMethod();}

调用我们声明的方法

public class MainActivity extends AppCompatActivity {    static {        System.loadLibrary("math");    }    private int[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};    private TextView sumTxt;    private TextView printArr;    private TextView callStaticMethodTV;    private TextView callInstanceMethodTV;    @SuppressLint("DefaultLocale")    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        sumTxt = (TextView) findViewById(R.id.sun_txt);        printArr = (TextView) findViewById(R.id.print_arr);        callStaticMethodTV = (TextView) findViewById(R.id.CallJavaStaticMethod);        callInstanceMethodTV = (TextView) findViewById(R.id.CallJavaInstanceMethod);        IntArray intArray = new IntArray();        callStaticMethodTV.setText(intArray.callJavaStaticMethod());//avoid the java method        callInstanceMethodTV.setText(intArray.callJavaInstaceMethod());        int sum = intArray.sumArray(array);        Toast.makeText(this, "The Attays sum is " + sum, Toast.LENGTH_SHORT).show();        sumTxt.setText("The Attays sum is " + sum);        int[][] arr = intArray.initInt2DArray(3);        StringBuilder stringBuilder = new StringBuilder();        for (int i = 0; i < 3; i++) {            for (int j = 0; j < 3; j++) {                stringBuilder.append(String.format("arr[%d][%d] = %d\n", i, j, arr[i][j]));                System.out.format("arr[%d][%d] = %d\n", i, j, arr[i][j]);            }        }        printArr.setText(stringBuilder.toString());    }}

在java中我们创建一个类并且写了一个静态方法,一个私有方法

/** * Created by blueZhang on 16/6/30. * * @Author: BlueZhang * @date: 16/6/30 */public class JavaStaticMethod {    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);    }}

现在准备工作已经做好了一部分,我们要做的事情就是通过native 方法调用JavaStaticMethod类中的两个方法,对于这两个方法我们将在native方法中通过反射机制获取静态方法以及创建JavaStaticMethod的对象调用私有方法并将native中传递过来的参数通过java方法进行输出。
下面生成我们的.h 文件如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_bluezhang_ndkarraytest_IntArray */#ifndef _Included_com_example_bluezhang_ndkarraytest_IntArray#define _Included_com_example_bluezhang_ndkarraytest_IntArray#ifdef __cplusplusextern "C" {#endif/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    sumArray * Signature: ([I)I */JNIEXPORT jint JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_sumArray  (JNIEnv *, jobject, jintArray);/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    initInt2DArray * Signature: (I)[[I */JNIEXPORT jobjectArray JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_initInt2DArray  (JNIEnv *, jobject, jint);/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    callJavaStaticMethod * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_callJavaStaticMethod  (JNIEnv *, jclass);/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    callJavaInstaceMethod * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_callJavaInstaceMethod  (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif

下面开始写我们在C中的实现

//// Created by blueZhang on 16/6/28.//#include "sum.h"#include <stdlib.h>#include <stdio.h>#include <string.h>/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    sumArray * Signature: ([I)I */JNIEXPORT jint JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_sumArray        (JNIEnv *env, jobject obj, jintArray j_array) {    jint i, sum = 0;    jint *c_array;    jint arr_len;    //1. 获取数组长度    arr_len = (*env)->GetArrayLength(env, j_array);    //2. 根据数组长度和数组元素的数据类型申请存放java数组元素的缓冲区    c_array = (jint *) malloc(sizeof(jint) * arr_len);    //3. 初始化缓冲区    memset(c_array, 0, sizeof(jint) * arr_len);    //4. 拷贝Java数组中的所有元素到缓冲区中    (*env)->GetIntArrayRegion(env, j_array, 0, arr_len, c_array);    for (i = 0; i < arr_len; i++) {        sum += c_array[i];  //5. 累加数组元素的和    }    free(c_array);  //6. 释放存储数组元素的缓冲区    return sum;//      jint i, sum = 0;//      jint *c_array;//      jint arr_len;//      // 可能数组中的元素在内存中是不连续的,JVM可能会复制所有原始数据到缓冲区,然后返回这个缓冲区的指针//      c_array = (*env)->GetIntArrayElements(env,j_array,NULL);//      if (c_array == NULL) {//          return 0;   // JVM复制原始数据到缓冲区失败//      }//      arr_len = (*env)->GetArrayLength(env,j_array);//      printf("arr_len = %d\n", arr_len);//      for (i = 0; i < arr_len; i++) {//          sum += c_array[i];//      }//      (*env)->ReleaseIntArrayElements(env,j_array, c_array, 0); // 释放可能复制的缓冲区//      return sum;}/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    initInt2DArray * Signature: (I)[[I */JNIEXPORT jobjectArray JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_initInt2DArray        (JNIEnv *env, jobject obj, jint size) {    jobjectArray result;    jclass clsIntArray;    jint i, j;    // 1.获得一个int型二维数组类的引用    clsIntArray = (*env)->FindClass(env, "[I");    if (clsIntArray == NULL) {        return NULL;    }    // 2.创建一个数组对象(里面每个元素用clsIntArray表示)    result = (*env)->NewObjectArray(env, size, clsIntArray, NULL);    if (result == NULL) {        return NULL;    }    // 3.为数组元素赋值    for (i = 0; i < size; ++i) {        jint buff[256];        jintArray intArr = (*env)->NewIntArray(env, size);        if (intArr == NULL) {            return NULL;        }        for (j = 0; j < size; j++) {            buff[j] = i + j;        }        (*env)->SetIntArrayRegion(env, intArr, 0, size, buff);        (*env)->SetObjectArrayElement(env, result, i, intArr);        (*env)->DeleteLocalRef(env, intArr);    }    return result;}/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    callJavaStaticMethod * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_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/example/bluezhang/ndkarraytest/JavaStaticMethod");    if (clazz == NULL) {        return (*env)->NewStringUTF(env, "Class is null ,it can't be find !");    }    // 2、从clazz类中查找callStaticMethod方法    mid_static_method = (*env)->GetStaticMethodID(env, clazz, "callStaticMethod",                                                  "(Ljava/lang/String;I)V");    if (mid_static_method == NULL) {        printf("找不到callStaticMethod这个静态方法。");        return (*env)->NewStringUTF(env, "找不到callStaticMethod这个静态方法");    }    // 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);    return (*env)->NewStringUTF(env, "加载这个StaticMethod 成功");}/* * Class:     com_example_bluezhang_ndkarraytest_IntArray * Method:    callJavaInstaceMethod * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_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/example/bluezhang/ndkarraytest/JavaStaticMethod");    if (clazz == NULL) {        printf("找不到'com.example.bluezhang.ndkarraytest.ClassMethod'这个类");        return(*env)->NewStringUTF(env, "找不到'com.example.bluezhang.ndkarraytest.ClassMethod'这个类");    }    // 2、获取类的默认构造方法ID    mid_construct = (*env)->GetMethodID(env, clazz, "<init>", "()V");    if (mid_construct == NULL) {        printf("找不到默认的构造方法");        return (*env)->NewStringUTF(env, "找不到默认的构造方法");    }    // 3、查找实例方法的ID    mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");    if (mid_instance == NULL) {        return  (*env)->NewStringUTF(env, "找不到callInstanceMethod这个静态方法");    }    // 4、创建该类的实例    jobj = (*env)->NewObject(env, clazz, mid_construct);    if (jobj == NULL) {        printf("在com.study.jnilearn.ClassMethod类中找不到callInstanceMethod方法");        return  (*env)->NewStringUTF(env, "找不到callInstanceMethod这个静态方法");    }    // 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);    return (*env)->NewStringUTF(env, "加载Java中的方法成功");}

代码解析:
AccessMethod.java是程序的入口,在main方法中,分别调用了callJavaStaticMethod和callJavaInstaceMethod这两个native方法,用于测试native层调用MethodClass.java中的callStaticMethod静态方法和callInstanceMethod实例方法,这两个方法的返回值都为Void,参数都有两个,分别为String和int

(*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);
该函数接收4个参数:
env:JNI函数表指针
cls:调用该静态方法的Class对象
methodID:方法ID(因为一个类中会存在多个方法,需要一个唯一标识来确定调用类中的哪个方法)
参数4:方法实参列表
根据函数参数的提示,分以下四步完成Java静态方法的回调:
第一步:调用FindClass函数,传入一个Class描述符,JVM会从classpath路径下搜索该类,并返回jclass类型(用于存储Class对象的引用)。注意ClassMethod的Class描述符为com/study/jnilearn/ClassMethod,要将.(点)全部换成/(反斜杠)
(*env)->FindClass(env, "com/example/bluezhang/ndkarraytest/JavaStaticMethod");
第二步:调用GetStaticMethodID函数,从ClassMethod类中获取callStaticMethod方法ID,返回jmethodID类型(用于存储方法的引用)。实参clazz是第一步找到的jclass对象,实参”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的三种实参的函数原型:

oid (JNICALL *CallStaticVoidMethod)       (JNIEnv *env, jclass cls, jmethodID methodID, ...);     void (JNICALL *CallStaticVoidMethodV)       (JNIEnv *env, jclass cls, jmethodID methodID, va_list args);     void (JNICALL *CallStaticVoidMethodA)  

第四步、释放局部变量

// 删除局部引用  (*env)->DeleteLocalRef(env,clazz);  (*env)->DeleteLocalRef(env,str_arg);  ```虽然函数结束后,JVM会自动释放所有局部引用变量所占的内存空间。但还是手动释放一下比较安全,因为在JVM中维护着一个引用表,用于存储局部和全局引用变量,经测试在Android NDK环境下,这个表的最大存储空间是512个引用,如果超过这个数就会造成引用表溢出,JVM崩溃。在PC环境下测试,不管申请多少局部引用也不释放都不会崩,我猜可能与JVM和Android Dalvik虚拟机实现方式不一样的原因。所以有申请就及时释放是一个好的习惯!(局部引用和全局引用在后面的文章中会详细介绍)二、callInstanceMethod实例方法实现说明

JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod
(JNIEnv *env, jclass cls)

定位到AccessMethod.c43行:

(*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);

CallVoidMethod函数的原型如下:

void (JNICALL *CallVoidMethod) (JNIEnv *env, jobject obj, jmethodID methodID, …);

该函数接收4个参数:         env:JNI函数表指针           obj:调用该方法的实例methodID:方法ID       参数4:方法的实参列表根据函数参数的提示,分以下六步完成Java静态方法的回调:第一步、同调用静态方法一样,首先通过FindClass函数获取类的Class对象第二步、获取类的构造方法ID,因为创建类的对象首先会调用类的构造方法。这里以默认构造方法为例

(*env)->GetMethodID(env,clazz, “”,”()V”);
代表类的构造方法名称,()V代表无参无返回值的构造方法(即默认构造方法)

第三步、调用GetMethodID获取callInstanceMethod的方法ID

(*env)->GetMethodID(env, clazz, “callInstanceMethod”, “(Ljava/lang/String;I)V”);

第四步、调用NewObject函数,创建类的实例对象

(*env)->NewObject(env,clazz,mid_construct);

第五步、调用CallVoidMethod函数,执行ClassMethod.callInstanceMethod方法调用,str_arg和200是方法实参

str_arg = (*env)->NewStringUTF(env,”我是实例方法”);
(*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);

同JNI调用Java静态方法一样,JVM针对所有数据类型的返回值都定义了相关的函数(CallXXXMethod),如:CallIntMethod、CallFloatMethod、CallObjectMethod等,也同样提供了支持三种类型实参的函数实现,以CallVoidMethod为例,如下是jni.h头文件中该函数的原型:

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开头,后面紧跟类的全路径名(需将.全部替换成/),以分号结尾。
总结:

1、调用静态方法使用CallStaticXXXMethod/V/A函数,XXX代表返回值的数据类型。如:CallStaticIntMethod
2、调用实例方法使用CallXXXMethod/V/A函数,XXX代表返回的数据类型,如:CallIntMethod
3、获取一个实例方法的ID,使用GetMethodID函数,传入方法名称和方法签名
4、获以一个静态方法的ID,使用GetStaticMethodID函数,传入方法名称和方法签名
5、获取构造方法ID,方法名称使用””
6、获取一个类的Class实例,使用FindClass函数,传入类描述符。JVM会从classpath目录下开始搜索。
7、创建一个类的实例,使用NewObject函数,传入Class引用和构造方法ID
8、删除局部变量引用,使用DeleteLocalRef,传入引用变量
9、方法签名格式:(形参参数列表)返回值类型。注意:形参参数列表之间不需要用空格或其它字符分隔
10、类描述符格式:L包名路径/类名;,包名之间用/分隔。如:Ljava/lang/String;
11、调用GetMethodID获取方法ID和调用FindClass获取Class实例后,要做异常判断

1 0
原创粉丝点击