zoukankan      html  css  js  c++  java
  • Android Studio NDK 新手教程(5)--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("
    "+helloJNI.getString(bean));
            helloJNI.ModifyBean(bean);
            tv.append("
    "+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对象返回。
  • 相关阅读:
    linux网络编程系列TCP及常用接口分析
    Linux网络编程系列TCP状态分析
    常见的HTTP 1.1状态代码及含义
    修改android SDK 模拟器(avd) 内存大小
    Android应用研发核心竞争力
    网路编程——阻塞式&&非阻塞式
    URI、URL和URN之间的区别与联系
    初识android——四大组件
    无依赖的combobox组件(autocomplete组件)
    为什么JS没有catchMyException或类似的方法
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8716651.html
Copyright © 2011-2022 走看看