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中配置好工具

image-20210508102623652
image-20210508102715572
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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容