Android安卓中jni与Java之间传递复杂的自定义数据结构

本文详细介绍了Android中JNI与Java之间如何传递复杂的自定义数据结构,包括JNI的基本数据类型、签名、函数签名及API接口函数的使用,并通过示例展示了静态/非静态成员函数的组合、父类成员函数的调用,以及特殊数据类型如字符串和数组的处理。同时,文章探讨了如何封装C++类为Java接口,处理复杂的自定义结构,并给出了测试结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文【Android安卓中封装opencv jni代码为Java类】简单介绍了jni代码封装为Java接口代码,直接使用opencv的Mat对象的nativeObj作为指针传递进行赋值,以及传递基础类型参数。

本文将实现更为复杂的结构对象传递,首先介绍jni基本的数据类型、接口函数和使用示例,再根据一个示例c++封装Java接口,最后说明jni的复杂数据结构传递实现。

1、JNI介绍

jni作为Java和c/c++交互的桥梁,需要在jni中接收/回传java中的数据对象,以及通过反射调用java中的方法。使用ndk将c/c++代码编译为so库,通过jin调用c/c++代码,java中加载so库从而调用native实现,这里不做详细介绍。

1.1、数据类型、签名

1.1.1、基本数据类型

数据类型分为基本数据类型、引用数据类型(数组、类等)两大类。基本类型可以直接在jni中透传,列表如下

Java基本类型 Native类型 说明
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

1.1.2、引用数据类型

引用类型以不透明的方式传递给native代码,而不是以原生数据类型的形式呈现,因此引用类型不能直接修改和使用。主要的三个引用类型列表如下,

Java引用类型 Native类型 说明
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象

所有的基本数据类型和引用数据类型的数组形式,都是引用类型。jni提供了与这些引用类型密切相关的一组API,这些API通过JNIEnv借口指针提供给原生函数。

1.1.3、签名

这里主要说明数据类型的签名,函数签名再后介绍。签名实际就是以一定规则将数据类型描述为字符串,便于在jni的api接口中通过反射机制获取或设置java中的数据变量。

类型签名 Java类型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
V void
Lfully-qualified-class fully-qualified-class
[type type[]

基本类型的签名 多为首字母大写,这个好理解。

class对象的签名,采用其全限定名,即使用 “L完全匹配类名包括包名和类名(用左斜杠 / 代替点号.);” 拼接而成。例如 Java中的String类型,包名为 java.lang.String,其签名为 “Ljava/lang/String;”。

子类的签名 由 “父类签名&子类名;” 拼接,例如java中ObjectInputStream.GetField的签名为 “Ljava/io/ObjectInputStream$GetField;”。

数组类型的签名 其元素的签名为 “[元素签名” 拼接而成。
例如Java中的int[],类型签名为"[I";opencv的类数组Rect[]的签名为 “[org/opencv/core/Rect”。

1.2、函数签名

同变量一样,通过jni接口函数获取函数,其签名规则为 (函数所有参数签名)函数返回值签名”。多个参数签名之间不需要空格或分隔符,例如

long f (int n, String s, int[] arr); // 签名为 (ILjava/lang/String;[I)J
boolean showMsg(View v, String msg); // 签名为 (Landroid/View;Ljava/lang/String;)Z

1.3、API接口函数

在C/C++本地代码中访问Java端的代码,一个常见的应用就是获取类的属性和调用类的方法,为了在C/C++中表示属性和方法,JNI在jni.h头文件中定义了jfieldIDjmethodID类型来,分别代表Java端的属性和方法。

访问或者设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfeldID,然后才能在本地代码中进行Java属性操作;同样,需要调用Java端的方法时,也是需要取得代表该方法的jmethodID才能进行Java方法调用。

1.3.1、属性

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

jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)

void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
void SetCharField(jobject obj, jfieldID fieldID, jchar value)
void SetShortField(jobject obj, jfieldID fieldID, jshort value)
void SetIntField(jobject obj, jfieldID fieldID, jint value)
void SetLongField(jobject obj, jfieldID fieldID, jlong value)
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)

// 静态成员
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)

jtype GetStaticTypeField(jclass clazz, jfieldID fieldID)
void SetStaticTypeField(jclass clazz, jfieldID fieldID, jtype value)

获取成员变量的 jfieldID 都需要通过类jclass获取非静态成员变量的访问和修改,必须通过实例化的jobject进行操作,而静态成员变量直接通过类jclass操作即可。

1.3.2、方法

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)   // 根据成员函数的字符串、成员函数签名获取方法id
jtype CallTypeMethod(jobject obj, jmethodID methodID, ...)  // 调用返回值类型jtype的函数

// 静态成员函数
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
jtype CallStaticTypeMethod(jclass clazz, jmethodID methodID, ...)  

和成员变量一样,两种类型成员函数的jmethodID都是通过类jclass获取非静态成员函数需要通过实例化的jobject访问静态成员函数直接通过类jclass访问

1.3.3、类对象相关

jclass FindClass(const char* name);  // 通过类完整路径获取类class
jclass GetObjectClass(jobject obj);  // 通过类对象获取class
jobject AllocObject(jclass clazz);   // 创建classd对象,仅分配内存
jobject NewObject(jclass clazz, jmethodID methodID, ...); // 创建class对象,分配内存并调用有参/无参构造函数

字符串String、数组Array的说明见后面简单示例。

1.4、简单的示例

1.4.1、静态/非静态 + 成员/函数 的组合

这里演示 静态/非静态 + 成员/函数 的组合情况native使用,以TestJNIBean的代码为例:

public class TestJNIBean{
   
   
    static {
   
    System.loadLibrary("native-lib"); }
	
    public int field;
    public static int staticField;

    public  String describe() {
   
    return "非静态方法"; }
    public static String staticDescribe(){
   
    return "静态方法"; }
    
    public native String testCallMethod();  //非静态
    public static native String testStaticCallMethod();//静态
}

cpp中文件的实现

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_TestJNIBean_testCallMethod(JNIEnv *env, jobject thiz) {
   
   
    // 非静态函数,参数thiz就是当前类实例对象
    jclass  a_class = env->GetObjectClass(thiz);     //因为是非静态的,所以要通过GetObjectClass获取对象

    jfieldID fieldId = env->GetFieldID(a_class, "field", "I");  // 非静态方法中获取非静态成员
    jint field =  env->GetIntField(thiz, fieldId);              // 通过实例化对象获取
    env->SetIntField(thiz, fieldId, 1);

    jfieldID staticFieldId = env->GetStaticFieldID(a_class, "staticField", "I");
    jint staticField = env->GetStaticIntField(a_class, staticFieldId);
    env->SetStaticIntField(a_class, staticFieldId, 1);

    jmethodID a_method = env->GetMethodID(a_class,"describe","()Ljava/lang/String;");// 通过GetMethod方法获取方法的methodId.
    jobject jobj = env->AllocObject(a_class);                                         // 对jclass进行实例,相当于java中的new
    jstring pring= (jstring)(env)->CallObjectMethod(jobj,a_method);                 // 类调用类中的方法
    char *print=(char*)(env)->GetStringUTFChars(pring,0);                           // 转换格式输出。
    return env->NewStringUTF(print
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值