Android Studio NDK 新手教程--Java对象的传递与改动

Posted zhchoutai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Studio NDK 新手教程--Java对象的传递与改动相关的知识,希望对你有一定的参考价值。

概述

本文主要Java与C++之间的对象传递与取值。包括传递Java对象、返回Java对象、改动Java对象、以及性能对照。

通过JNIEnv完毕数据转换

Java对象是存在于JVM虚拟机中的,而C++是脱离JVM而执行的,假设在C++中訪问和使用Java中的对象。必定会使用JNIEnv这个桥梁。事实上通过以下的代码非常easy看出,这样的訪问方式和Java中的反射十分雷同。

这里定义一个简单Java对象用于下文測试:

package com.example.wastrel.hellojni;
/**
 * Created by wastrel on 2016/8/24.
 */
public class Bean {
    private String msg;
    private int what;

    public Bean(String msg,int what)
    {
        this.msg=msg;
        this.what=what;
    }


    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getWhat() {
        return what;
    }

    public void setWhat(int what) {
        this.what = what;
    }

    @Override
    public String toString() {
        return "Msg:"+msg+";What:"+what;
    }
}

从C++中创建一个Java对象并返回

    //Java中的native方法声明
    public native Bean newBean(String msg,int what);
//C++中的方法实现
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
        (JNIEnv *env, jobject obj, jstring msg,jint what){
    //先找到class
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
    //在实际应用中应该确保你的class、method、field存在。

降低此类推断。

if(bean_clz==NULL) { LOGE("can‘t find class"); return NULL; } //获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V jmethodID bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V"); if(bean_init==NULL) { LOGE("can‘t find init function"); return NULL; } //然后调用构造函数获得bean jobject bean=env->NewObject(bean_clz,bean_init,msg,what); return bean; }

注:假设提示找不到NULL 请include<stddef.h>

C++中解析Java对象

//java方法Native声明
public native String getString(Bean bean);
//C++中的方法实现
JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getString
        (JNIEnv *env, jobject obj,jobject bean){
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");

//这部分是通过get函数去获取相应的值 
//    jmethodID bean_getmsg=env->GetMethodID(bean_clz,"getMsg","()Ljava/lang/String;");
//    jmethodID bean_getwhat=env->GetMethodID(bean_clz,"getWhat","()I");
//    jstring jmsg=(jstring)env->CallObjectMethod(bean,bean_getmsg);
//    jint what=env->CallIntMethod(bean,bean_getwhat);

//这部分是通过类的成员变量直接取获取值。你可能注意到在Java中定义的变量都是private修饰的。但在反射的调用下是毫无作用的。
    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
    jstring jmsg=(jstring)env->GetObjectField(bean,bean_fmsg);
    jint  what=env->GetIntField(bean,bean_fwhat);

//将拿到的值拼装一个String返回回去
    const char * msg=env->GetStringUTFChars(jmsg,NULL);
    char *str=new char[1024];
    sprintf(str,"Msg:%s;What:%d(From C++)",msg,what);
    jstring rs=env->NewStringUTF(str);
    delete  []str;
    env->ReleaseStringUTFChars(jmsg,msg);
    return rs;
}

注:sprintf函数包括在stdio.h头文件里

C++中改动Java对象属性值

//java方法Native声明
public native void ModifyBean(Bean bean);
//C++实现
JNIEXPORT void JNICALL Java_com_example_wastrel_hellojni_HelloJNI_ModifyBean
        (JNIEnv *env, jobject obj,jobject bean){
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
    jstring msg=env->NewStringUTF("Modify in C++");
    //又一次设置属性
    env->SetObjectField(bean,bean_fmsg,msg);
    env->SetIntField(bean,bean_fwhat,20);
    return;
}

结果图

//java中调用代码
        HelloJNI helloJNI=new HelloJNI();
        Bean bean=helloJNI.newBean("This is from C++ bean",10);
        tv.setText(bean.toString());
        bean=new Bean("This is from Java bean",15);
        tv.append("\n"+helloJNI.getString(bean));
        helloJNI.ModifyBean(bean);
        tv.append("\n"+bean.toString());

技术分享图片

Java中new Object和C++中new Object的性能对照

以下我们通过一个測试函数来比較通过两种方式的性能,这里能够毫无疑问的告诉你,Java一定比C++的快。那么这个对照的意义就在于,使用C++创建Java对象的时候会不会造成不可接受的卡顿。
这里使用的測试机是华为Mate7,详细硬件配置可自行百度。
測试函数例如以下:

     void Test(int count)
    {
        long startTime=System.currentTimeMillis();
        for (int i=0;i<count;i++)
        {
            new Bean("123",i);
        }
        long endTime=System.currentTimeMillis();
        Log.e("Java","Java new "+count+"s waste "+(endTime-startTime)+"ms");

        HelloJNI helloJNI=new HelloJNI();
       startTime=System.currentTimeMillis();
        for (int i=0;i<count;i++)
        {
            helloJNI.newBean("123",i);
        }
        endTime=System.currentTimeMillis();
        Log.e("C++","C++ new "+count+"s waste "+(endTime-startTime)+"ms");
    }

測试结果:

Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 38ms

Java: Java new 10000s waste 6ms
C++: C++ new 10000s waste 79ms

Java: Java new 50000s waste 56ms
C++: C++ new 50000s waste 338ms

Java: Java new 100000s waste 60ms
C++: C++ new 100000s waste 687ms

通过结果能够看出,通过C++来new对象比Java慢了足足10倍左右。可是从时间上来讲。假设仅仅是在C++中new一个Java对象。几个微秒的时间差距全然是能够忽略不计的。

或许有人就会说。C++慢那么多是由于每次都在FindClass,GetMethodId。而在程序执行过程中这两个值是不会改变的。听起来确实有这样一个原因。以下我们将C++中的代码稍作改动缓存jclass和jmethodId。
改动后的newBean函数:

//用静态变量缓存
static jclass bean_clz=NULL;
static jmethodID bean_init=NULL;
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
        (JNIEnv *env, jobject obj, jstring str,jint what){
    //先找到class
    if(bean_clz==NULL)
    {
        jclass  _bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
        bean_clz=(jclass)env->NewGlobalRef(_bean_clz);
    }
    //获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V
    if(bean_init==NULL)
    {
        bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");
    }
    //然后调用构造函数获得bean
    jobject bean=env->NewObject(bean_clz,bean_init,str,what);
    return bean;
}

 你可能发现了缓存方法ID和缓存jclass似乎不一样。那是由于jclass事实上是java.lang.Class对象,而方法ID是JNI中定义的一个结构体。假设这里不使用env—>NewGlobalRef()函数声明其是一个全局引用的话,在执行的时候可能就会报错:JNI ERROR (app bug): accessed stale local reference 0x5900021;表明在Jvm中该对象已经被回收了。引用已经失效了。而NewGlobalRef的作用就在于告诉JVM。C++中一直持有该引用,请不要回收。显然这又引发了另外一个问题。你须要在你不须要该引用的时候告诉JVM,那么就须要调用env->DelGlobalRef()。当然你也能够不调用。那么该Java对象将在你的程序关闭的时候被回收。

測试结果:

Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 18ms

Java: Java new 10000s waste 5ms
C++: C++ new 10000s waste 24ms

Java: Java new 50000s waste 44ms
C++: C++ new 50000s waste 121ms

Java: Java new 100000s waste 65ms
C++: C++ new 100000s waste 259ms

这次的结果表明,假设缓存方法ID和jclass能缩短一半的时间。但仍然不如Java快。这也非常好理解。C++创建Java对象终于还是通过Java创建的,重复的通过反射去创建自然不如自身创建来得快。

总结

  • JNI中想訪问Java Object方法签名、类名和变量名十分重要,一旦确定了就不要轻易单方面改动Java中的定义。由于这会导致JNI找不到相关的方法或类等,而引发JNI错误。

  • 尽管JNI提供了各种方法来完毕Java的反射操作,可是请酌情使用,由于这会让Java代码与C++代码之间过度依赖。
  • 当你须要返回C++中的结构体数据的时候,能够考虑把结构体转换成相应的Java对象返回。




以上是关于Android Studio NDK 新手教程--Java对象的传递与改动的主要内容,如果未能解决你的问题,请参考以下文章

Android Studio NDK 入门教程--被NDK支持的C++运行库

Android Studio NDK 入门教程--优雅的在C++中输出Logcat

Android Studio NDK 入门教程--JNI动态注册本地方法

Android Studio NDK 入门教程--Java与C++之间的类型签名

无法配置NDK android studio并获取错误

Android Studio NDK 入门教程--Java对象的传递与修改