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

采得百花成蜜后,为谁辛苦为谁甜。这篇文章主要讲述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); jintwhat=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());

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

文章图片

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 38msJava: Java new 10000s waste 6ms C++: C++ new 10000s waste 79msJava: Java new 50000s waste 56ms C++: C++ new 50000s waste 338msJava: 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 18msJava: Java new 10000s waste 5ms C++: C++ new 10000s waste 24msJava: Java new 50000s waste 44ms C++: C++ new 50000s waste 121msJava: Java new 100000s waste 65ms C++: C++ new 100000s waste 259ms

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

总结
  • JNI中想訪问Java Object方法签名、类名和变量名十分重要,一旦确定了就不要轻易单方面改动Java中的定义。由于这会导致JNI找不到相关的方法或类等,而引发JNI错误。
    【Android Studio NDK 新手教程--Java对象的传递与改动】
  • 尽管JNI提供了各种方法来完毕Java的反射操作,可是请酌情使用,由于这会让Java代码与C++代码之间过度依赖。
  • 当你须要返回C++中的结构体数据的时候,能够考虑把结构体转换成相应的Java对象返回。

    推荐阅读