Android-JNI开发系列《六》jni与java的交互

人间观察
1024-程序员节
愿各位程序员历尽千帆,归来仍是少年。

这片文章本来不打算写的,因为在前面的文章多多少少的提到了jni和java的交互,但是为了让知识体系更健全写,还是梳理下,算是jni和java的在交互上的一个总结吧。
两者的交互归纳起来主要就是两种。

  1. java调用jni。比如:传递基本数据,复杂对象等
  2. jni调用java。比如回调,异常,调用java方法/成员变量,构造java对象等等

java调用jni-传到复杂对象到jni中

我们新建一个java的对象,然后传递到jni中,在jni中获取该对象的属性值。

java对象如下

package com.bj.gxz.jniapp.methodfield;

import java.io.Serializable;

/**
 * Created by guxiuzhong on 2020/10/24.
 */
public class AppInfo implements Serializable {
    private static final String TAG = "AppInfo";
    private String versionName;
    public int versionCode;
    public long size;

    public AppInfo(String versionName) {
        this.versionName = versionName;
    }

    public AppInfo(String versionName, int versionCode) {
        this.versionName = versionName;
        this.versionCode = versionCode;
    }

    public String getVersionName() {
        return versionName;
    }

    public void setVersionName(String versionName) {
        this.versionName = versionName;
    }

    public int getVersionCode() {
        return versionCode;
    }

    public void setVersionCode(int versionCode) {
        this.versionCode = versionCode;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public long getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "AppInfo{" +
                "versionName='" + versionName + '\'' +
                ", versionCode=" + versionCode +
                ", size=" + size +
                '}';
    }
}

jni接口为

public native void getAppInfoFromJava(AppInfo appInfo);

对应jni的方法是

extern "C" JNIEXPORT void  JNICALL
Java_com_bj_gxz_jniapp_methodfield_JNIMethodField_getAppInfoFromJava(JNIEnv *env, jobject instance,
                                                                     jobject obj) {
                 // ...                                                    
}

最后一个参数obj就是对应java传过来的对象AppInfo。 因为在jni中所有的java对象都是jobject。对应关系,这里再贴一下:

基本数据类型

java与Native映射关系如下表所示:

Java类型 Native 类型 Description
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 not applicable

引用数据类型

外面的为jni中的,括号中的java中的。

  • jobject
    • jclass (java.lang.Class objects)
    • jstring (java.lang.String objects)
    • jarray (arrays)
      • jobjectArray (object arrays)
      • jbooleanArray (boolean arrays)
      • jbyteArray (byte arrays)
      • jcharArray (char arrays)
      • jshortArray (short arrays)
      • jintArray (int arrays)
      • jlongArray (long arrays)
      • jfloatArray (float arrays)
      • jdoubleArray (double arrays)
  • jthrowable (java.lang.Throwable objects)

上面的层次中的jni的引用类型代表了继承关系,jbooleanArray继承jarray,jarray继承jobject,最终都继承jobject。

ok。

jni调用java对象的方法

调用对象的某个方法 Call<返回类型>Method<传参类型>,比如调用AppInfogetVersionCode对应的就是CallIntMethod,调用setVersionCode对应的就是CallVoidMethod方法,

Call<返回类型>Method<传参类型> Native 类型 java类型
CallVoidMethod() CallVoidMethodA() CallVoidMethodV() void void
CallObjectMethod() CallObjectMethodA() CallObjectMethodV() jobject object
CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV() jboolean boolean
CallByteMethod() CallByteMethodA() CallByteMethodV() jbyte byte
CallCharMethod() CallCharMethodA() CallCharMethodV() jchar char
CallShortMethod() CallShortMethodA() CallShortMethodV() jshort short
CallIntMethod() CallIntMethodA() CallIntMethodV() jint int
CallLongMethod() CallLongMethodA() CallLongMethodV() jlong long
CallFloatMethod() CallFloatMethod A() CallFloatMethodV() jlong long
CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV() jfloat float
如果java方法是静态的如下 - -
CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV() jshort short
省略其它方法… - -

具体可以参考官网:官网开发文档

每一种返回类型对应3个方法,唯一不同的是输入参数的传入形式不同。所以getVersionName对应的就是CallObjectMethod

CallObjectMethod参数:
obj:某个 Java 对象实例
methodID:指定方法的ID
args:输入参数列表,方法如果没有参数则不写

特别注意
如果你调用的是Java对象的方法CallxxxxMethod第一个参数是某个 Java 对象实例。但是如果你调用的是静态方法,则第一个参数是jclass。静态方法并不属于某一个对象。

methodID的获取通过GetMethodID,在jni.h头文件中函数声明原型为:

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

GetMethodID参数
clazz: 对应java的class类; 为java类的全类名比如把点改为/即com.bj.gxz.jniapp.methodfield.AppInfo改为 com/bj/gxz/jniapp/methodfield/AppInfo
const char* name 方法名字;
const char* sig 方法的签名,方法的签名可以通过javap -s xxx.class获取。

示例获取getVersionName的完整代码如下:

    // 根据java对象获取对象对应的class
    jclass cls = env->GetObjectClass(obj);
    // 获取&调用java方法
    jmethodID getVersionName_mid = env->GetMethodID(cls, "getVersionName", "()Ljava/lang/String;");
    jstring versionName = (jstring) env->CallObjectMethod(obj, getVersionName_mid);
        char *c_string = const_cast<char *>(env->GetStringUTFChars(versionName, 0));
    LOG_D("versionName=%s", c_string);

看着很简单吧。

jni获取java对象的属性值

获取java对象的属性对应的值的方法为GetXXXXField,XXXX 为数据类型,比如GetIntFieldGetShortField等等,如果不是基本型则为GetObjectField,如果属性为static的则加上Static,比如GetStaticIntFieldGetStaticObjectField。在jni中是不看成员变量的作用域的,不管你是privateprotectedpublic的,加finnal也一样,它都可以读取里面的值,和反射不一样。

GetXXXXField参数
obj:某个 Java 对象实例
jfieldID:指定属性的ID

GetFieldID参数
clazz:对象java对象的class
const char* name:属性名字
const char* sig:数据类型描述符

数据类型描述符java和jni的对应关系如下,来:

Java类型 类型描述符
int I
long J
byte B
short S
char C
float F
double D
boolean Z
void V
数组 [
二维数组 [[
其他引用类型 L+类全名+;

例子如下:

    // 获取java对象的属性值
    jfieldID versionCode_fieldId = env->GetFieldID(cls, "versionCode", "I");
    int versionCode = env->GetIntField(obj, versionCode_fieldId);
    LOG_D("versionCode=%d", versionCode);

    // 获取java对象的属性值
    jfieldID size_fieldId = env->GetFieldID(cls, "size", "J");
    long size = env->GetLongField(obj, size_fieldId);
    LOG_D("size=%ld", size);

    // 获取java静态属性的值
    jfieldID tag_fieldId = env->GetStaticFieldID(cls, "TAG", "Ljava/lang/String;");
    jstring tag_java = (jstring) env->GetStaticObjectField(cls, tag_fieldId);
    char *tag_c_string = const_cast<char *>(env->GetStringUTFChars(tag_java, 0));
    LOG_D("TAG=%s", tag_c_string);

运行后:

        JNIMethodField jniMethodField = new JNIMethodField();

        AppInfo javaInfo = new AppInfo("com.wg.com", 30);
        javaInfo.setSize(500);
        jniMethodField.getAppInfoFromJava(javaInfo);
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionName=com.wg.com
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionCode=30
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: size=500
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: TAG=AppInfo

属性的获取和方法的获取都差不多。

jni中构造java对象&调用java方法

关于的回调异常可以参考前面的文章。

Android-JNI开发系列《二》-在jni层的线程中回调到java层

Android-JNI开发系列《三》-异常处理

我们在jni中创建一个java对象,其实就是调用java的构造方法,只不过是构造方法的函数签名为固定值<init>. 在jni中创建复杂对象(任何java对象)用NewObject方法,同样有不同参数的NewObjectVNewObjectA

jni.h函数声明原型为:

 jobject NewObject(jclass clazz, jmethodID methodID, ...)

参数
clazz java类的class;
methodID 指定方法的ID;
… 参数 支持多个参数;

clazz和methodID同上文方法中的介绍。

示例如下:

    // 获取java的class
    jclass cls = env->FindClass("com/bj/gxz/jniapp/methodfield/AppInfo");

    // 创建java对象,就是调用构造方法,构造方法的方法签名固定为<init>
    jmethodID mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
    jobject obj = env->NewObject(cls, mid, env->NewStringUTF("com.gxz.com"));

    // 给定方法名字和签名,调用方法
    jmethodID setVersionCode_mid = env->GetMethodID(cls, "setVersionCode", "(I)V");
    env->CallVoidMethod(obj, setVersionCode_mid, 1);

    // 给定属性名字和签名,设置属性的值
    jfieldID size_field_id = env->GetFieldID(cls, "size", "J");
    env->SetLongField(obj, size_field_id, (jlong) 1000);

size的属性我们是通过SetLongField设置的,见名知义。这个和获取属性一样/调用java中的方法一样,它也有类似SetLongFieldSetIntField,SetStaticIntField,SetObjectField等等,区分了基本类型,静态属性,引用类型。大家可以尝试一下,这里不多介绍了。
运行后:

        AppInfo info = jniMethodField.createAppInfoFromJni();
        Log.e(TAG, "info=" + info);
        

输出

        10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp E/JNI: info=AppInfo{versionName='com.gxz.com', versionCode=1, size=1000}

最后源代码:https://github.com/ta893115871/JNIAPP

猜你喜欢

转载自blog.csdn.net/ta893115871/article/details/109259341