Android Studio NDK 入门教程(5)--Java对象的传递与修改
2016-08-24 17:34
615 查看
概述
本文主要Java与C++之间的对象传递与取值。包括传递Java对象、返回Java对象、修改Java对象、以及性能对比。通过JNIEnv完成数据转换
Java对象是存在于JVM虚拟机中的,而C++是脱离JVM而运行的,如果在C++中访问和使用Java中的对象,必然会使用JNIEnv这个桥梁。其实通过下面的代码很容易看出,这种访问方式和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 入门教程(2)--Java与C++之间的简单数据转换与传递
- Android Studio NDK 入门教程(3)--Java与C++之间的类型签名
- Android Studio NDK 入门教程--JNI签名验证防止恶意调用
- Android Studio NDK 入门教程(1)--来自C 语言的String
- Android Studio NDK 入门教程(6)--JNI签名验证防止恶意调用
- Android Studio NDK 入门教程(8)--JNI动态注册本地方法
- Android Studio NDK 入门教程(1)--来自C 语言的String
- Android studio NDK开发 从入门到实践-之将java代码打包成jar包
- Android Studio NDK 入门教程(7)--被NDK支持的C++运行库
- Android Studio NDK 入门教程
- Android Studio NDK 入门教程(4)--优雅的在C++中输出Logcat
- Android高手进阶教程(十七)之---Android中Intent传递对象的两种方法(Serializable,Parcelable)!
- 【Android中级教程(三)之不同Activity之间的数据传递---Bundle对象的使用】的改进!
- Android高手进阶教程(十七)之---Android中Intent传递对象的两种方法(Serializable,Parcelable)!
- 8步教你打开Android之门 NDK入门教程
- Android高手进阶教程(十七)之---Android中Intent传递对象的两种方法(Serializable,Parcelable)!
- 8步教你打开Android之门 NDK入门教程
- 8步教你打开Android之门 NDK入门教程
- 8步教你打开Android之门 NDK入门教程
- Android 基于NDK的JNI开发 C调用java和java调用C的进阶教程