Android-NDK学习记录5-Jni调用实例方法

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

上一篇看了jni调用静态方法和修改静态字段,这一篇学习了jni调用实例方法和修改实例字段

  • 调用实例方法,步骤:
  1. 找到类:利用FindClass,找到类
  2. 找到要调用的方法id:利用GetMethodID,找到方法id
  3. 创建实例对象:利用实例对象的构造方法id来创建
  4. 使用实例对象去调用对应的Method:CallVoidMethod
  • 修改实例字段,步骤:
  1. 找到类:利用FindClass,找到类
  2. 找到要修改的字段id:利用GetFieldID,找到字段id
  3. 创建实例对象:利用实例对象的构造方法id来创建
  4. 设置字段值:SetIntField

有上一篇做基础,咱们直接来看看:

  • 先来一个Java类,后面使用它来创建实例,和使用其中字段与方法
/**
 * jni实例调用测试:
 * 1. 测试调用打印年龄和姓名,预期打印 0 和null
 * 2. 通过修改字段,设置age和name的值,然后再打印
 * 3. 重新创建一个对象,初始化方法中传入性别,调用打印性别的方法
 */
public class ObjectTest {
    private static final String TAG = ObjectTest.class.getSimpleName();

    /**
     * 性别
     */
    private String sex;
    /**
     * 年龄
     */
    private int age;
    /**
     * 姓名
     */
    private String name;

    /**
     * 无参数构造方法
     */
    public ObjectTest() {
    }

    /**
     * 带参数构造方法
     *
     * @param sex 性别
     */
    public ObjectTest(String sex) {
        this.sex = sex;
    }

    /**
     * 用于打印age和name
     */
    public void logInfo() {
        Log.i(TAG, "age:" + age + ",name:" + name);
    }

    /**
     * 用于打印性别
     */
    public void logSex() {
        Log.i(TAG, "性别:" + sex);
    }
}

一. 调用实例方法

(1) 调用无参构造方法创建对象方式
  1. 找到jclass:依旧是使用全路径名
    jclass class_Objtest = env->FindClass("shixin/ndkdemo/obj_test/ObjectTest");
    if (class_Objtest == NULL) {
        LOGE("没找到类");
        return;
    }
  1. 找到方法id:一会要调用的logInfo的方法id
jmethodID meth_id_logInfo = env->GetMethodID(class_Objtest, "logInfo", "()V");
  1. 创建实例
  • jni没有直接方法创建Java对象,那么就通过使用它构造方法来创建,构造方法的方法名为:
<init>
  • 创建实例使用NewObject方法来创建,具体代码如下:
/*由于jni没有直接方法创建java对象,所以通过构造方法来创建*/
    //获取构造方法,这里使用的是无参构造方法
    jmethodID mtheod_construct_init = env->GetMethodID(class_Objtest, "<init>", "()V");
    //创建实例Object,NewObject,参数:类、构造方法id
    jobject object_ = env->NewObject(class_Objtest, mtheod_construct_init);
    if (object_ == NULL) {
        LOGE("创建对象失败");
        return;
    }
  1. 调用实例方法:对象也创建好了,那么就可以调用它的方法了咯,使用CallVoidMethod就行:
//调用方法,CallVoidMethod,参数:实例、要调用的方法id
    env->CallVoidMethod(object_, meth_id_logInfo);

就这样,就能调用对象的方法了,结合上面的Java的logInfo情况,大家应该也能猜到这次的打印是什么,因为age和name都是初始值:

ndkdemo I/ObjectTest: age:0,name:null
(2) 调用有参构造方法创建对象方式

调用有参数构造方法来初始化:除了无参数构造方法,也可以调用有参数的构造方法,区别就是在创建对象的时候,比如这里通过调用对象的有sex参数的构造方法来初始化对象:

  1. 找到有参数构造方法的id:
//这里调用sex的构造方法:参数:类、构造方法名、签名
    jmethodID mtheod_construct_init_sex = env->GetMethodID(class_Objtest, "<init>",
                                                           "(Ljava/lang/String;)V");
  1. 创建对象:
//创建一个字符串对象,女
jstring string_sex = env->NewStringUTF("女");
//使用NewObject来创建对象,参数:类、构造方法id、参数值
    jobject object_sex = env->NewObject(class_Objtest, mtheod_construct_init_sex, string_sex);
    if (object_sex == NULL) {
        LOGE("带参构造方法创建对象失败");
        return;
    }
  1. 找到打印性别的方法id,再调用:
//6.1 找到打印性别的方法id
    jmethodID mid_log_sex = env->GetMethodID(class_Objtest, "logSex", "()V");
    if (mid_log_sex == NULL) {
        LOGE("没找到打印性别这个方法");
        return;
    }
    //6.2 开始调用
    env->CallVoidMethod(object_sex, mid_log_sex);
  1. 打印:
ndkdemo I/ObjectTest: 性别:女

二. 修改实例字段

前面提到了无参数构造的时候,因为age和name都是初始值,正好,这里来给他们赋值一下

  1. 找到字段id:
//先找到字段的id,参数:类、字段名、字段的签名,这里age是int的,所以是I
    jfieldID f_id_age = env->GetFieldID(class_Objtest, "age", "I");
    if (f_id_age == NULL) {
        LOGE("没找到age字段");
        return;
    }
    
    //同样的方式,获取name的字段id
    jfieldID f_id_name = env->GetFieldID(class_Objtest, "name", "Ljava/lang/String;");
    if (f_id_name == NULL) {
        LOGE("没找到name字段");
        return;
    }
  1. 给age和name字段赋值:

这里的age是12,name是"小宝贝"

//设置age值,参数:实例对象、字段id、值
    env->SetIntField(object_, f_id_age, age);
    //因为String是Object类型,所以使用SetObjectField
    env->SetObjectField(object_, f_id_name, name_);
  1. 再试试打印age和name方法:

meth_id_logInfo是之前的logInfo方法id

//修改了值之后,再来验证调用一次logInfo,查看打印
    env->CallVoidMethod(object_, meth_id_logInfo);
  1. 打印:
ndkdemo I/ObjectTest: age:12,name:小宝贝

看打印,这就能看到,咱们是赋值成功了

三. 整体代码

  1. java中的native方法,与调用传值:
//测试调用事例方法
        testObjectMethodUse(12,"小宝贝");

    /**
     * 实例方法调用
     */
    private native void testObjectMethodUse(int age, String name);
  1. jni完整实现:
/**
 * 实例方法调用测试
 */
extern "C"
JNIEXPORT void JNICALL
Java_shixin_ndkdemo_MainActivity_testObjectMethodUse(JNIEnv *env, jobject instance, jint age,
                                                     jstring name_) {

    //1. 利用FindClass,找到jclass
    jclass class_Objtest = env->FindClass("shixin/ndkdemo/obj_test/ObjectTest");
    if (class_Objtest == NULL) {
        LOGE("没找到类");
        return;
    }
    //2. 利用GetMethodID,找到方法id
    jmethodID meth_id_logInfo = env->GetMethodID(class_Objtest, "logInfo", "()V");

    /*3. 创建实例对象,由于jni没有直接方法创建java对象,所以通过构造方法来创建*/
    //3.1 获取构造方法,这里使用的是无参构造方法
    jmethodID mtheod_construct_init = env->GetMethodID(class_Objtest, "<init>", "()V");
    //3.2 创建实例Object,NewObject,参数:类、构造方法id
    jobject object_ = env->NewObject(class_Objtest, mtheod_construct_init);
    if (object_ == NULL) {
        LOGE("创建对象失败");
        return;
    }

    //4. 调用方法,CallVoidMethod,参数:实例、要调用的方法id
    env->CallVoidMethod(object_, meth_id_logInfo);

    /*5. 试试修改字段.这里我们利用java调用传进来的age和name值来修改ObjectTest中的age和name*/
    //5.1 先找到字段的id,参数:类、字段名、字段的签名,这里age是int的,所以是I
    jfieldID f_id_age = env->GetFieldID(class_Objtest, "age", "I");
    if (f_id_age == NULL) {
        LOGE("没找到age字段");
        return;
    }
    //5.2 设置age值,参数:实例对象、字段id、值
    env->SetIntField(object_, f_id_age, age);
    //5.3 同样的方式,获取name的字段id和设置值
    jfieldID f_id_name = env->GetFieldID(class_Objtest, "name", "Ljava/lang/String;");
    if (f_id_name == NULL) {
        LOGE("没找到name字段");
        return;
    }
    //因为String是Object类型,所以使用SetObjectField
    env->SetObjectField(object_, f_id_name, name_);
    //5.4 修改了值之后,再来验证调用一次logInfo,查看打印
    env->CallVoidMethod(object_, meth_id_logInfo);


    /*6. 测试下带参数的构造方法,传入性别,再调用打印性别的方法*/
    jmethodID mtheod_construct_init_sex = env->GetMethodID(class_Objtest, "<init>",
                                                           "(Ljava/lang/String;)V");
    jstring string_sex = env->NewStringUTF("女");
    jobject object_sex = env->NewObject(class_Objtest, mtheod_construct_init_sex, string_sex);
    if (object_sex == NULL) {
        LOGE("带参构造方法创建对象失败");
        return;
    }
    //6.1 找到打印性别的方法id
    jmethodID mid_log_sex = env->GetMethodID(class_Objtest, "logSex", "()V");
    if (mid_log_sex == NULL) {
        LOGE("没找到打印性别这个方法");
        return;
    }
    //6.2 开始调用
    env->CallVoidMethod(object_sex, mid_log_sex);


    /*释放类和对象*/
    env->DeleteLocalRef(class_Objtest);
    env->DeleteLocalRef(object_);

    env->DeleteLocalRef(string_sex);
    env->DeleteLocalRef(object_sex);
}
  1. 输出:
ndkdemo I/ObjectTest: age:0,name:null
ndkdemo I/ObjectTest: age:12,name:小宝贝
ndkdemo I/ObjectTest: 性别:女

四. 总结

  1. 调用实例方法和修改字段与静态的挺类似,区别就在于要创建实例,使用创建的实例去访问其中的方法和字段,而静态的则只需要类,这个我想不用过多解释,Java特性大家都应该了解
  2. 调用方法:CallXXMethod。其中XX代表返回值,Void代表无返回,Int代表返回Int
  3. 修改字段:SetXXField。其中XX代表字段类型,Int代表int类型,Object代表对象类型。
  4. 另外jni去调用实例方法、字段的时候,java中的private这种限定符就没意义了,private的字段jni中一样可以直接修改,或许jni的方式就是认为内部调用的吧。还好,final字段的修改不会成功,不然颠覆了,哈哈

猜你喜欢

转载自blog.csdn.net/ddxxii/article/details/84489886