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
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjint signed32 bits
long jlongsigned64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidnot 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()voidvoid
CallObjectMethod() CallObjectMethodA() CallObjectMethodV()jobjectobject
CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV()jbooleanboolean
CallByteMethod() CallByteMethodA() CallByteMethodV()jbytebyte
CallCharMethod() CallCharMethodA() CallCharMethodV()jcharchar
CallShortMethod() CallShortMethodA() CallShortMethodV()jshortshort
CallIntMethod() CallIntMethodA() CallIntMethodV()jintint
CallLongMethod() CallLongMethodA() CallLongMethodV()jlonglong
CallFloatMethod() CallFloatMethod A() CallFloatMethodV()jlonglong
CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV()jfloatfloat
如果java方法是静态的如下--
CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV()jshortshort
省略其它方法…--

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

每一种返回类型对应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类型类型描述符
intI
longJ
byteB
shortS
charC
floatF
doubleD
booleanZ
voidV
数组[
二维数组[[
其他引用类型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

本文链接http://www.taodudu.cc/news/show-1781966.html