程序员社区

Android JNI开发-对象操作

Android JNI开发-对象操作

对象操作基本步骤

Jni是沟通Java世界和Native世界的纽带,Java层调用本地方法只用调用Java中定义的本地(native)方法就可用了,那么,本地的C/C++代码如何调用Java层的代码呢?这就是本章节对象操作要解决阐述的内容。

一般的,C/C++层要调用Java层代码,需要进行以下步骤。

  1. 获取Java层对应的jclass,通过jclass来获取Java类的方法,属性。
  2. 获取Java层对象引用,如果Java层有传引用下来,则可用直接使用,否则就需要在C/C++层调用创建对象的接口来创建Java层对应类的对象。调用Java静态函数不需要对象引用。
  3. 调用Java层类的静态方法,或者Java层对象的方法。
  4. 处理返回值。

函数列表

Jni中类操作相关的函数包括以下函数:

函数 说明
DefineClass 从原始类数据的缓冲区中加载类
FindClass 查找类
GetSuperclass 查找父类
IsAssignableFrom 类型判断

关于类名

Jni在加载或查找Java层类时,需要通过类的全名来进行加载,该类的全名为类包含包名的全路径,比如 java.lang.String类,那么类的全名则为java/lang/String

对应的C++函数定义如下:

/**
 * 从原始类数据的缓冲区中加载类
 * @param name 类的全名,必须是被UTF-8编码过的字符串。
 * @param loader 类加载器
 * @param buf 包含 .class 文件数据的缓冲区
 * @return java类对象。发生错误时返回NULL
 */
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)

/**
 *  查找类
 * @param name 类的全名,必须是被UTF-8编码过的字符串。
 * @return 
 */
jclass FindClass(const char* name)

/**
 * 获取父类
 * @param clazz Java类对象
 * @return java类对象。clazz的父类或NULL
 */
jclass GetSuperclass(jclass clazz)

/**
 * 确定clazz1的对象是否可安全地强制转换为clazz2
 * @param clazz1 类的对象
 * @param clazz2 类的对象
 * @return 如果满足以下任一条件,则返回JNI_TRUE:
 *         1. 如果clazz1和clazz2是同一个Java类。 
 *         2. 如果clazz1是clazz2的子类
 *         3. 如果clazz1是clazz2接口的实现类
 */
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)

访问对象的变量

准备工作

public class TestCallJavaObject {

    public native void putString(String hello); // 传递String对象

    public native void putObject(Student student); // 传递引用类型,传递对象

    public native void insertObject(); // 凭空创建Java对象



    public static void main(String[] args) {

    }
}

Javah 生成native方法头文件,可以在idea中配置好工具

Android JNI开发-对象操作插图
image-20210508102623652
Android JNI开发-对象操作插图1
image-20210508102715572
Android JNI开发-对象操作插图2
image-20210508102757323
///------------------???calljava.cpp???-------------------
#include "com_jni_calljava_TestCallJavaObject.h"
#include <string>
#include <iostream>

using namespace std;

JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_putObject
        (JNIEnv *env, jobject thiz, jobject student) {

    // 1.寻找类 Student
    jclass studentClass1 = env->FindClass("com/jni/calljava/Student"); // 第一种
    jclass studentClass2 = env->GetObjectClass(student); // 第二种

    cout << "studentClass1=" << studentClass1 << endl;
    cout << "studentClass1=" << studentClass2 << endl;

}
public static void main(String[] args) {

        TestCallJavaObject testCallJavaObject = new TestCallJavaObject();
        Student student = new Student();

        testCallJavaObject.putObject(student);

    }

RUN>

*********************** ?运行结果? **************************
studentClass1=0x7ff60a80dad0
studentClass1=0x7ff60a80dad8

可以看到env->FindClassenv->GetObjectClass(student)两种方法获取的jclass对象内存地址一致,说明获取到的都是同一个对象,如果你懂Java反射,那么自然懂得获取的java对象的class对象,是反射的基础.

获取jmethodID

jmethodID GetMethodID(JNIEnv *env, jclass clazz,
                        const char *name, const char *sig);

参数

  • jclass clazz: Java类的Class对象,需要通过GetObjectClass()或者FindClass()获取。
  • const char * name: 方法名,需要通过GetMethodID()获取。
  • const char * sig: 方法签名。

返回值

  • 如果找到了对应的方法,就会返回一个jmethodID对象,否则返回NULL

注意: 如果要获取Java类的构造方法,参数const char *name的值为<init>,参数const char *sig的值为void (V)。这个是比较特殊的,需要注意。

调用对象的方法

根据Java方法返回的类型的不同,JNI有不同的函数来调用Java对象的方法。不过基本形式有三种

// C 函数原型
jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
// C++ 函数原型
jobject     CallObjectMethod(jobject, jmethodID, ...);
jobject     CallObjectMethodV(jobject, jmethodID, va_list);
jobject     CallObjectMethodA(jobject, jmethodID, const jvalue*);

可以看到这三类函数的区别在于传参的方式不同。

第一个函数和第三个函数其实原理都是一样的,不过最常用的应该就是第一个。

那么第二个函数就有点意思了,参数使用的是jvalue类型的数组作为参数。

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

jvalue是一个联合体,联合体中定义Java基本类型和引用类型的变量,因此就可以以数组的行为来传递参数,这下就明白了吧。

获取jfieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, 
                const char *name, const char *sig);

参数

  1. env: 指向JNIEnv的指针。
  2. clazz: Class对象,可以通过GetObjectClass()或者FindClass()获取。
  3. name: Class对象的某个变量的名字。
  4. sig: Class对象的变量的类型签名。

返回一个Class对象的变量,类型为jfieldIDClass对象的变量由参数namesig唯一标识。

Java反射一样,获取到了变量后,就可以通过这个变量获取到变量的值,也可以设置变量的值。

JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_putObject
        (JNIEnv *env, jobject thiz, jobject student) {

    // 1.寻找类 Student
    cout<<"---------寻找类 Student---------------"<< endl;
    jclass studentClass1 = env->FindClass("com/jni/calljava/Student"); // 第一种
    jclass studentClass2 = env->GetObjectClass(student); // 第二种

    cout << "studentClass1=" << studentClass1 << endl;
    cout << "studentClass1=" << studentClass2 << endl;


    cout<<"-----------GetMethodID------------"<< endl;
    // 2.Student类里面的函数规则  签名
    jmethodID setName = env->GetMethodID(studentClass1, "setName", "(Ljava/lang/String;)V");
    jmethodID getName = env->GetMethodID(studentClass1, "getName", "()Ljava/lang/String;");
    jmethodID showInfo = env->GetStaticMethodID(studentClass1, "showInfo", "(Ljava/lang/String;)V");

    // 3.调用 setName
    cout<<"-----------CallVoidMethod------------"<< endl;
    jstring value = env->NewStringUTF("AAAA");
    env->CallVoidMethod(student, setName, value);

    // 4.调用 getName
    jstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));
    const char *getNameValue = env->GetStringUTFChars(getNameResult, NULL);
    cout << ("调用到getName方法,值是:") << getNameValue << endl;

    // 5 访问对象的变量
    cout<<"------访问对象的变量---------"<< endl;
    jfieldID fieldID_mAge = env->GetFieldID(studentClass1, "age", "I");
    // 设置Java对象obj的变量mAge的值
    env->SetIntField(student, fieldID_mAge, 18);
    // 从Java对象obj中获取变量mAge的值
    jint age = env->GetIntField(student, fieldID_mAge);
    cout << "访问对象的变量 Age: " << age << std::endl;

}
package com.jni.calljava;
public class Student {


    public String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("Java setName name:" + name);
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("Java setAge age:" + age);
        this.age = age;
    }

    public static void showInfo(String info) {
        System.out.println("showInfo info:" + info);
    }

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

RUN>

*********************** ?运行结果? **************************
---------寻找类 Student---------------
studentClass1=0x7faba2d0a6f0
studentClass1=0x7faba2d0a6f8
-----------GetMethodID------------
-----------CallVoidMethod------------
Java setName name:AAAA
调用到getName方法,值是:AAAA
------访问对象的变量---------
访问对象的变量 Age: 18

对象操作

Jni中对象操作相关的函数主要包括:

函数 说明
AllocObject 直接创建Java对象
NewObject 根据某个构造函数来创建Java对象
NewObjectV 根据某个构造函数来创建Java对象
NewObjectA 根据某个构造函数来创建Java对象
GetObjectClass 获取指定类对象的类
IsInstanceOf 判断某个对象是否是某个“类”的子类

对应的C++函数定义如下:

/**
 * 不借助任何构造函数的情况下分配一个新的Java对象
 * @param clazz Java类对象
 * @return 返回一个Java对象实例,如果该对象无法被创建,则返回NULL
 */
jobject AllocObject(jclass clazz)

/**
 * 根据构造函数来创建Java对象
 * @param clazz Java类对象
 * @param methodID 构造函数的方法ID
 * @param ... 构造函数的输入参数(可变参数)
 * @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
 */
jobject NewObject(jclass clazz, jmethodID methodID, ...)

/**
 * 根据构造函数来创建Java对象
 * @param clazz Java类对象
 * @param methodID 构造函数的方法ID
 * @param args 构造函数的输入参数(va_list)
 * @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
 */
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)

/**
 * 根据构造函数来创建Java对象
 * @param clazz Java类对象
 * @param methodID 构造函数的方法ID
 * @param args 构造函数的输入参数(参数数组)
 * @return 返回一个Java对象实例,如果无法创建该对象,则返回NULL
 */
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args)

/**
 * 通过对象获取类
 * @param obj 类的对象
 * @return Java 类对象。
 */
jclass GetObjectClass(jobject obj)

/**
 * 判断某个对象是否是某个“类”的子类
 * @param obj Java对象实例
 * @param clazz Java类对象
 * @return 
 */
jboolean IsInstanceOf(jobject obj, jclass clazz)
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_insertObject
        (JNIEnv *env, jobject thiz) {

    // 1.通过包名+类名的方式 拿到 Student class  凭空拿class
    jclass studentClass = env->FindClass("com/jni/calljava/Student"); // 第一种

    // 2.通过student的class  实例化此Student对象   C++ new Student
    jobject studentObj = env->AllocObject(studentClass); // AllocObject 只实例化对象,不会调用对象的构造函数

    // 方法签名的规则
    jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
    jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");

    // 调用方法
    jstring strValue = env->NewStringUTF("HelloJni");
    env->CallVoidMethod(studentObj, setName, strValue);
    env->CallVoidMethod(studentObj, setAge, 18);

    jmethodID toStringMethod = env->GetMethodID(studentClass, "toString", "()Ljava/lang/String;");
    jstring toStringResult = static_cast<jstring>(env->CallObjectMethod(studentObj, toStringMethod));
    const char *value = env->GetStringUTFChars(toStringResult, nullptr);
    cout << value << endl;

    env->DeleteLocalRef(studentClass);
    env->DeleteLocalRef(studentObj);
}

RUN>

*********************** ?运行结果? **************************
Java setName name:HelloJni
Java setAge age:18
Student{name='HelloJni', age=18}
jclass dogClass;
JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_testQuote
        (JNIEnv *env, jobject thiz) {
    if (nullptr == dogClass) {
        // 升级全局引用: JNI函数结束也不释放,反正就是不释放,必须手动释放   ----- 相当于: C++ 对象 new、手动delete
        const char *dogStr = "com/jni/calljava/Dog";
        jclass temp = env->FindClass(dogStr);
        dogClass = static_cast<jclass>(env->NewGlobalRef(temp)); // 提升全局引用
        // 记住:用完了,如果不用了,马上释放,C C++ 工程师的赞美
        env->DeleteLocalRef(temp);
    }

    // <init> V  是不会变的

    // 构造函数一
    jmethodID init = env->GetMethodID(dogClass, "<init>", "()V");
    jobject dog = env->NewObject(dogClass, init);

    // 构造函数2
    init = env->GetMethodID(dogClass, "<init>", "(I)V");
    dog = env->NewObject(dogClass, init, 100);


    // 构造函数3
    init = env->GetMethodID(dogClass, "<init>", "(II)V");
    dog = env->NewObject(dogClass, init, 200, 300);

    // 构造函数4
    init = env->GetMethodID(dogClass, "<init>", "(III)V");
    dog = env->NewObject(dogClass, init, 400, 500, 600);

    env->DeleteLocalRef(dog); // 释放
}



JNIEXPORT void JNICALL Java_com_jni_calljava_TestCallJavaObject_delQuote
        (JNIEnv *env, jobject thiz) {
    if (dogClass != nullptr) {
        cout<<("全局引用释放完毕~~~~~~~~~????");
        env->DeleteGlobalRef(dogClass);
        dogClass = nullptr; // 最好给一个NULL,指向NULL的地址,不要去成为悬空指针,为了好判断悬空指针的出现
    }
}

RUN>

*********************** ?运行结果? **************************
Dog init...
Dog init... n1:100
Dog init... n1:200 n2:300
Dog init... n1:400 n2:500 n3:600
全局引用释放完毕~~~~~~~~~????
代码传送门

Demo-GitHub

赞(0) 打赏
未经允许不得转载:IDEA激活码 » Android JNI开发-对象操作

一个分享Java & Python知识的社区