Android NDk-JNi开发(五)、JNI与Java的相互调用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tongsiw/article/details/83514887

此篇幅主要讲解java调用jni的方法和jni调用java

一、Jni调用Java代码

jni可以调用java中的方法和java中的成员变量,因此JNIEnv定义了一系列的方法来帮助我们调用java的方法和成员变量。

JNI类型 C/C++类型 所表示的含义
jclass GetObjectClass(jobject obj) 获取对象obj的jclass
jclass FindClass(const char* name) 获取指定类的class,name是所需要的全类名。
如:FindClass(“java/util/Date”)
jobject NewObject(jclass clazz, jmethodID methodID, …) 获取指定类的对象。
如:要获取Date的对象:NewObject(FindClass(“java/util/Date”),“构造方法”)
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) 获取java类中成员变量的id。
第一个参数是对象的jclass;
第二个参数是java类中的成员变量名;
第三个参数是java类中成员变量名的签名
jobject GetObjectField(jobject obj, jfieldID fieldID) 获取jaa类中成员变量的值。
第一个参数是java类的对象
第二个参数就是上述的java类中成员变量的id
void SetObjectField(jobject obj, jfieldID fieldID, jobject value) 给java类的成员变量赋值。
第一个参数:类的对象;
第二个参数:成员变量的id;
第三个参数:给java成员变量所赋的值
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) 获取java类的方法id
第一个参数:对象的jclass;
第二个参数:方法名;
第三个参数:java方法签名
jobject env->CallObjectMethod(jobject jobject, jmethodID methodID…) 调用java类的方法

以上就是jni调用java类的大部分方法,如果是静态的成员变量和静态方法,可以使用***GetStaticMethodID、CallStaticObjectMethod等***。就是在上述表格中的相应方法中加个static。

上述中有一个重要的点就是:构造方法的方法id获取,GetMethodID第二个参数传***"< init >"***,这个是固定写法,不能变

上述中还有一个重要的点就是:方法签名(GetFieldID,GetMethodID中的需要的sig参数),这玩意需要记住的,如果记不住,可以通过 javap -s -p 命令去获取,关于javap命令不多说了,下面给一个实例命令行:

javap -s -p E:\1_Study_Space\6_JNI\2_JNI\app\build\intermediates\classes\debug\tsw\demo\a2_jni\Student.class

下面是jni调用java类方法的实例代码


//jni调用java方法
public native void jniCallJava();



public class Student {
    public String name;
    public String sex;

    public Student() {

    }

    public Student(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}


extern "C"
JNIEXPORT void JNICALL
Java_tsw_demo_a2_1jni_TestJni_jniCallJava(JNIEnv *env, jobject instance) {
    //------------------通过无参构造获取Student类的对象,同时调用setName方法给对象赋值------------------
    jobject jobj_student;
    jmethodID jmid_tostring;
    //1、获取java类Student的jclass
    jclass jcla_student = env->FindClass("tsw/demo/a2_jni/Student");
    //2、获取Student的无参构造方法id。构造方法第二个参数,固定传 <init>,不能变;第三个参数是方法签名
    jmethodID jmid_student = env->GetMethodID(jcla_student, "<init>", "()V");
    //3、获取student对象
    jobj_student = env->NewObject(jcla_student, jmid_student);
    //4、获取Student类setName方法id。第二个参数是方法名;第三个参数是方法签名
    jmethodID jmid_setname = env->GetMethodID(jcla_student, "setName", "(Ljava/lang/String;)V");
    //5、调用Student类setName方法。因为Student的setName是void类型,所以用CallVoidMethod。
    //java类的方法返回什么类型,就用相应类型的Call<Type>Method方法。Call<Type>Method表示CallVoidMethod、CallLongMethod、CallObjectMethod等。
    env->CallVoidMethod(jobj_student, jmid_setname, env->NewStringUTF("zhangsan"));

    //6、调用Student的toString方法
    jmid_tostring = env->GetMethodID(jcla_student, "toString", "()Ljava/lang/String;");
    jstring j_tostring = (jstring) env->CallObjectMethod(jobj_student, jmid_tostring);
    LOGE("setName方法给对象赋值: %s", env->GetStringUTFChars(j_tostring, JNI_FALSE));

    //--------------通过有参构造,给Student对象赋值----------------------
    //获取Student类的有参构造方法id
    jmethodID jmid_student_hvae = env->GetMethodID(jcla_student, "<init>","(Ljava/lang/String;Ljava/lang/String;)V");

    //通过有参构造方法创建Student对象,并同时赋值
    jobj_student = env->NewObject(jcla_student, jmid_student_hvae, env->NewStringUTF("lisi"),env->NewStringUTF("women"));

    jmid_tostring = env->GetMethodID(jcla_student, "toString", "()Ljava/lang/String;");
    jstring j_str = (jstring) env->CallObjectMethod(jobj_student, jmid_tostring);
    LOGE("有参构造给对象赋值: %s", env->GetStringUTFChars(j_str, JNI_FALSE));


    //--------------修改Student对象成员变量name的值(上面student对象的name=lisi,将lisi修改成wangwu)----------------------
    jfieldID jfid_name = env->GetFieldID(jcla_student, "name", "Ljava/lang/String;");
    env->SetObjectField(jobj_student,jfid_name,env->NewStringUTF("wangwu"));
    jmid_tostring = env->GetMethodID(jcla_student, "toString", "()Ljava/lang/String;");
    jstring j_fieldstr = (jstring) env->CallObjectMethod(jobj_student, jmid_tostring);
    LOGE("修改对象成员变量的值: %s", env->GetStringUTFChars(j_fieldstr, JNI_FALSE));
    ///最后带有Nonvirtual的方法,如:CallNonvirtualObjectMethod调用的是父类的方法,而不是子类的,这个要注意
}

二、Java调用jni中代码

1、Java调用jni方法,并传一个基本类型的参数

java代码

public native int operatInt(int num);
public static native int operatStaticInt(int num);



extern "C"
JNIEXPORT jint JNICALL
Java_tsw_demo_a2_1jni_TestJni_operatInt(JNIEnv *env, jobject instance, jint num) {
    num = num + 1;
    return num;
}

extern "C"
JNIEXPORT jint JNICALL
Java_tsw_demo_a2_1jni_TestJni_operatStaticInt(JNIEnv *env, jclass type, jint num) {
    num = num + 1;
    return num;
}

由上述代码可以发现:

  • 当java类的native方法是非静态的时候,jni方法的第二个参数是jobject类型的。
  • 当java类的native方法是静态的时候,jni方法的第二个参数是jclass类型的。
  • 第三个参数就是java传过来的int类型的参数,javaint类型与jnijint类型相对应。jni返回的也应当是jint类型

2、Java调用jni方法,并传一个数组类型的参数

首先说下JNI为我们定义了哪些数组类型:如下

jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArray、jobjectArray

上面是JNI定义的类型,并不是C/C++的数组,所以需要将上述的数组类型转换成C/C++类型的数组。而JNIEnv定义了一系列的方法来转换:如下
以jobject为例,每个类型的数组JNIEnv中都有定义,就不赘述了。

JNI类型 C/C++类型 所表示的含义
jsize GetArrayLength(jarray array) 获得数组的长度
jobject GetObjectArrayElement(jobjectArray array, jsizeindex) 获取数组的指针
jobjectArray NewObjectArray(jsize length, jclass elementClass,jobject initialElement) 创建一个指定大小的数组
. SetObjectArrayElement(jobjectArray array, jsize index, jobject value) 设置数组中的元素

具体的请看代码中的注释

(1)、操作int类型的数组
//传递一个int类型数组并修改数组里的值,同时创建一个长度为num的数组返回
public native int[] operatIntArray(int[] nums,int num);


extern "C"
JNIEXPORT jintArray JNICALL
//传递一个数组,修改数组里的值,同时创建一个长度为num的数组返回
Java_tsw_demo_a2_1jni_TestJni_operatIntArray(JNIEnv *env, jobject instance, jintArray nums_,jint num) {
    //---------下面就是修改数组的值--------------------------------------------------------------------------------
    
    //1、获取数组的指针
    // GetIntArrayElements两个参数,第一个是jintArray没啥说的,第二个是 jboolean* 表示是否复制,一般NULL和JNI_FALSE是一样的表示不复制,JNI_TRUE表示复制
    jint *nums = env->GetIntArrayElements(nums_, NULL);
   
    //2、获取数组的长度
    jsize length = env->GetArrayLength(nums_);
    
    //3、修改数组的值
    for (int i = 0; i < length; ++i) {
        nums[i] = i;
    }
   
    //4、释放资源。这个一定得写,感觉不仅仅是释放资源,应该还有一个java和c同步的操作,
    env->ReleaseIntArrayElements(nums_, nums, 0);
    for (int i = 0; i < length; ++i) {
        LOGE("数组的值 :%d", nums[i])
    }
    //---------下面是创建一个新的数组----------------------------------------------------------------------------
    
    //1、创建一个长度为num的jint空数组
    jintArray jarr = env->NewIntArray(num);
   
    //2、获取数组的指针
    jint *carr = env->GetIntArrayElements(jarr, JNI_FALSE);
   
    //3、给数组赋值
    for (int i = 0; i < num; i++) {
        carr[i] = i * 2;
    }
    
    //4、释放资源。
    env->ReleaseIntArrayElements(jarr, carr, 0);
    return jarr;
}

(2)、操作String类型的数组
//传递一个String类型数组并修改数组里的值,同时创建一个长度为num的数组返回
public native String[] operatStringArray(String[] nums,int num);


extern "C"
JNIEXPORT jobjectArray JNICALL
Java_tsw_demo_a2_1jni_TestJni_operatStringArray(JNIEnv *env, jobject instance, jobjectArray nums, jint num) {
    //---------下面就是修改数组的值--------------------------------------------------------------------------------
  
    //1、获取数组的长度
    jsize length = env->GetArrayLength(nums);

    //2、这里要注意:获取Object类型的数组和基本类型的数组中的元素方式是不一样的。
    // 基本类型的数组首先是通过GetIntArrayElements方法获取数组指针,然后通过遍历的方式拿到数组中的元素。
    // Object类型的数组是通过GetObjectArrayElement(jobjectArray array, jsize index)方法拿到数组中的每一个元素。
    // Object类型数组的赋值只能通过SetObjectArrayElement(jobjectArray array, jsize index, jobject value)方法;
  
    for (int i = 0; i < length; i++) {
        
        //获取Object类型数组中的元素。String是Object类型数组
        jstring jstr = (jstring) env->GetObjectArrayElement(nums, i);
       
        //下面是c字符串拼接操作。
        char *cstr = (char *) env->GetStringUTFChars(jstr, JNI_FALSE);
        char *addstr = "new";
        strcat(cstr, addstr);
        
        //将拼接之后的新的字符串重新转换成jstring
        jstring jnew_str = env->NewStringUTF(cstr);
       
        //将转换之后jstring,也就是上面的jnew_str重新赋值给Object数组
        env->SetObjectArrayElement(nums,i,jnew_str);
    }
    
    //下面是打印到logcat日志
    for (int i = 0; i < length; i++) {
        jstring jstr = (jstring) env->GetObjectArrayElement(nums, i);
        char *cstr = (char *) env->GetStringUTFChars(jstr, JNI_FALSE);
        LOGE("数组的值 :%c", cstr[0]);
        LOGE("数组的值 :%c", cstr[1]);
        LOGE("数组的值 :%c", cstr[2]);
        LOGE("数组的值 :%c", cstr[3]);
    }
   //---------下面就是创建一个Object的数组--------------------------------------------------------------------
   
    //获取String所属类,一般为java/lang/String就可以了。其实这里应该能想到如果是一个别java类,这里应该写这个java类的全类名。
    jclass strClass = (env)->FindClass("java/lang/String");
   
    //创建一个长度为num的String数组
    jobjectArray jStrArray = env->NewObjectArray(num, strClass, JNI_FALSE);
    char* tempArr[] = { " I ",  " create", " in ", " JNI" };
    for(int i = 0;i < num ;i++ ){
        //将c字符串转换成jstring
        jstring jstr_create = env->NewStringUTF(tempArr[i]);
      
        //将jstring设置到String数组中
        env->SetObjectArrayElement(jStrArray,i,jstr_create);
    }
    return jStrArray;//将新创建好的String数组返回给java
}
(3)、操作自定义Java类的的数组

public class Student {
    public String name;
    public String sex;
}


//获取一个自定义java对象
public native Student getStudent();
//获取一个自定义长度的java对象数组
public native Student[] getStudents(int num);



extern "C"
JNIEXPORT jobject JNICALL
Java_tsw_demo_a2_1jni_TestJni_getStudent(JNIEnv *env, jobject instance) {
    //其实这个内容前面基本已经说过,要想通过jni生成一个java类对象,其实最主要的一步就是获取那个java类的对象
    //1、获取到java类Student的class
    jclass jclsStu = env->FindClass("tsw/demo/a2_jni/Student");

    //2、第二部获取Student的构造方法,第二个参数固定传<init>,第三个方法是签名,无参构造方法的签名就是 ()V
    jmethodID jstu_constructor_mid = env->GetMethodID(jclsStu, "<init>", "()V");

    //3、获取Student的对象,后面给对象赋值就完成了java类对象的创建
    jobject jobjStu = env->NewObject(jclsStu, jstu_constructor_mid);

    //4、获取Student的成员变量name的jfieldID
    //GetFieldID方法的第三个参数是Student类成员变量name的签名,前面已经说过,这里就不赘述了。
    jfieldID jname_fid = env->GetFieldID(jclsStu, "name", "Ljava/lang/String;");

    //5、给Student的成员变量赋值
    jstring jstr_name = env->NewStringUTF("zhangsan");
    env->SetObjectField(jobjStu, jname_fid, jstr_name);

    //如上4、5步骤
    jfieldID jsex_fid = env->GetFieldID(jclsStu, "sex", "Ljava/lang/String;");
    jstring jstr_sex = env->NewStringUTF("man");
    env->SetObjectField(jobjStu, jsex_fid, jstr_sex);
    return jobjStu;
}

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_tsw_demo_a2_1jni_TestJni_getStudents(JNIEnv *env, jobject instance, jint num) {
    //1、获取jclass
    jclass jclaStu = env->FindClass("tsw/demo/a2_jni/Student");

    //2、获取Student的构造方法
    jmethodID jstu_constructor_mid = env->GetMethodID(jclaStu, "<init>", "()V");

    //3、获取Student成员变量的fieldId
    jfieldID jfid_name = env->GetFieldID(jclaStu, "name", "Ljava/lang/String;");
    jfieldID jfid_sex = env->GetFieldID(jclaStu, "sex", "Ljava/lang/String;");

    char* names[] = {"zhangsna","lisi","wangwu"};
    char* sexs[] = {"man","woman","man"};
    //4、创建一个空的object数组
    jobjectArray jarr_stu = env->NewObjectArray(num, jclaStu, JNI_FALSE);
    for (int i = 0; i < num; i++) {
        //创建Student对象
        jobject jobjStu = env->NewObject(jclaStu, jstu_constructor_mid);
        //给对象赋值
        env->SetObjectField(jobjStu,jfid_name,env->NewStringUTF(names[i]));
        env->SetObjectField(jobjStu,jfid_sex,env->NewStringUTF(sexs[i]));
        //给数组赋值
        env->SetObjectArrayElement(jarr_stu,i,jobjStu);
    }
    return jarr_stu;
}

3、Java调用jni方法,并获取一个List类型的集合

public class Student {
    public String name;
    public String sex;
}



//获取一个集合
public native List<Student> getListStudent(int num);




extern "C"
JNIEXPORT jobject JNICALL
Java_tsw_demo_a2_1jni_TestJni_getListStudent(JNIEnv *env, jobject instance, jint num) {
    //1、获取ArrayList的class
    jclass jcls_arraylist = env->FindClass("java/util/ArrayList");
    //2、获取ArrayList的无参构造方法id,以便用来获取ArrayList的对象
    jmethodID jarraylist_constructor_mid = env->GetMethodID(jcls_arraylist, "<init>", "()V");
    //3、获取ArrayList的对象
    jobject jobj_arraylist = env->NewObject(jcls_arraylist, jarraylist_constructor_mid);

    //4、获取Arraylist的add方法
    jmethodID jmid_add = env->GetMethodID(jcls_arraylist, "add", "(Ljava/lang/Object;)Z");


    //接下来的步骤就是创建Student对象,然后给对象赋值并添加到Arraylist集合中去,步骤上面有,就不赘述了
    jclass jclaStu = env->FindClass("tsw/demo/a2_jni/Student");
    jmethodID jstu_constructor_mid = env->GetMethodID(jclaStu, "<init>", "()V");
    jfieldID jfid_name = env->GetFieldID(jclaStu, "name", "Ljava/lang/String;");
    jfieldID jfid_sex = env->GetFieldID(jclaStu, "sex", "Ljava/lang/String;");
    char* names[] = {"zhangsna","lisi","wangwu"};
    char* sexs[] = {"man","woman","man"};
    for (int i = 0; i < num; ++i) {
        jobject jobjStu = env->NewObject(jclaStu, jstu_constructor_mid);
        env->SetObjectField(jobjStu,jfid_name,env->NewStringUTF(names[i]));
        env->SetObjectField(jobjStu,jfid_sex,env->NewStringUTF(sexs[i]));
        //Student对象创建完成并赋值了,接下来就是调用Arraylist的add方法,将Student对象放到Arraylist集合中区
        //因为Arraylist的add方法会返回一个boolean值,所以使用CallBooleanMethod
        env->CallBooleanMethod(jobj_arraylist,jmid_add,jobjStu);
    }
    return jobj_arraylist;
}

代码下载

可以新建一个项目,把下载好的java文件放到项目目录下,其中的c++代码拷贝到native-lib.cpp文件中,然后修改jni中的方法名即可

猜你喜欢

转载自blog.csdn.net/tongsiw/article/details/83514887